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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 21:18:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 21:18:33 +0300
commitf64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch)
treea2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /spec/lib/gitlab/ci
parentbfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff)
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r--spec/lib/gitlab/ci/artifacts/metrics_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/build/cache_spec.rb105
-rw-r--r--spec/lib/gitlab/ci/build/context/build_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/context/global_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/charts_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb344
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/config/entry/need_spec.rb86
-rw-r--r--spec/lib/gitlab/ci/config/entry/needs_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/entry/product/parallel_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb229
-rw-r--r--spec/lib/gitlab/ci/jwt_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb287
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb282
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb56
-rw-r--r--spec/lib/gitlab/ci/reports/reports_comparer_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/status/composite_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb372
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb104
-rw-r--r--spec/lib/gitlab/ci/variables/collection/sort_spec.rb185
-rw-r--r--spec/lib/gitlab/ci/variables/collection/sorted_spec.rb259
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb386
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb263
33 files changed, 2508 insertions, 842 deletions
diff --git a/spec/lib/gitlab/ci/artifacts/metrics_spec.rb b/spec/lib/gitlab/ci/artifacts/metrics_spec.rb
new file mode 100644
index 00000000000..3a2095498ec
--- /dev/null
+++ b/spec/lib/gitlab/ci/artifacts/metrics_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Artifacts::Metrics, :prometheus do
+ let(:metrics) { described_class.new }
+
+ describe '#increment_destroyed_artifacts' do
+ context 'when incrementing by more than one' do
+ let(:counter) { metrics.send(:destroyed_artifacts_counter) }
+
+ it 'increments a single counter' do
+ subject.increment_destroyed_artifacts(10)
+ subject.increment_destroyed_artifacts(20)
+ subject.increment_destroyed_artifacts(30)
+
+ expect(counter.get).to eq 60
+ expect(counter.values.count).to eq 1
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/cache_spec.rb b/spec/lib/gitlab/ci/build/cache_spec.rb
new file mode 100644
index 00000000000..9188045988b
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/cache_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Build::Cache do
+ describe '.initialize' do
+ context 'when the multiple cache feature flag is disabled' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ end
+
+ it 'instantiates a cache seed' do
+ cache_config = { key: 'key-a' }
+ pipeline = double(::Ci::Pipeline)
+ cache_seed = double(Gitlab::Ci::Pipeline::Seed::Build::Cache)
+ allow(Gitlab::Ci::Pipeline::Seed::Build::Cache).to receive(:new).and_return(cache_seed)
+
+ cache = described_class.new(cache_config, pipeline)
+
+ expect(Gitlab::Ci::Pipeline::Seed::Build::Cache).to have_received(:new).with(pipeline, cache_config)
+ expect(cache.instance_variable_get(:@cache)).to eq(cache_seed)
+ end
+ end
+
+ context 'when the multiple cache feature flag is enabled' do
+ context 'when the cache is an array' do
+ it 'instantiates an array of cache seeds' do
+ cache_config = [{ key: 'key-a' }, { key: 'key-b' }]
+ pipeline = double(::Ci::Pipeline)
+ cache_seed_a = double(Gitlab::Ci::Pipeline::Seed::Build::Cache)
+ cache_seed_b = double(Gitlab::Ci::Pipeline::Seed::Build::Cache)
+ allow(Gitlab::Ci::Pipeline::Seed::Build::Cache).to receive(:new).and_return(cache_seed_a, cache_seed_b)
+
+ cache = described_class.new(cache_config, pipeline)
+
+ expect(Gitlab::Ci::Pipeline::Seed::Build::Cache).to have_received(:new).with(pipeline, { key: 'key-a' })
+ expect(Gitlab::Ci::Pipeline::Seed::Build::Cache).to have_received(:new).with(pipeline, { key: 'key-b' })
+ expect(cache.instance_variable_get(:@cache)).to eq([cache_seed_a, cache_seed_b])
+ end
+ end
+
+ context 'when the cache is a hash' do
+ it 'instantiates a cache seed' do
+ cache_config = { key: 'key-a' }
+ pipeline = double(::Ci::Pipeline)
+ cache_seed = double(Gitlab::Ci::Pipeline::Seed::Build::Cache)
+ allow(Gitlab::Ci::Pipeline::Seed::Build::Cache).to receive(:new).and_return(cache_seed)
+
+ cache = described_class.new(cache_config, pipeline)
+
+ expect(Gitlab::Ci::Pipeline::Seed::Build::Cache).to have_received(:new).with(pipeline, cache_config)
+ expect(cache.instance_variable_get(:@cache)).to eq([cache_seed])
+ end
+ end
+ end
+ end
+
+ describe '#cache_attributes' do
+ context 'when the multiple cache feature flag is disabled' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ end
+
+ it "returns the cache seed's build attributes" do
+ cache_config = { key: 'key-a' }
+ pipeline = double(::Ci::Pipeline)
+ cache = described_class.new(cache_config, pipeline)
+
+ attributes = cache.cache_attributes
+
+ expect(attributes).to eq({
+ options: { cache: { key: 'key-a' } }
+ })
+ end
+ end
+
+ context 'when the multiple cache feature flag is enabled' do
+ context 'when there are no caches' do
+ it 'returns an empty hash' do
+ cache_config = []
+ pipeline = double(::Ci::Pipeline)
+ cache = described_class.new(cache_config, pipeline)
+
+ attributes = cache.cache_attributes
+
+ expect(attributes).to eq({})
+ end
+ end
+
+ context 'when there are caches' do
+ it 'returns the structured attributes for the caches' do
+ cache_config = [{ key: 'key-a' }, { key: 'key-b' }]
+ pipeline = double(::Ci::Pipeline)
+ cache = described_class.new(cache_config, pipeline)
+
+ attributes = cache.cache_attributes
+
+ expect(attributes).to eq({
+ options: { cache: cache_config }
+ })
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb
index 61ca8e759b5..46447231424 100644
--- a/spec/lib/gitlab/ci/build/context/build_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/build_spec.rb
@@ -9,7 +9,9 @@ RSpec.describe Gitlab::Ci::Build::Context::Build do
let(:context) { described_class.new(pipeline, seed_attributes) }
describe '#variables' do
- subject { context.variables }
+ subject { context.variables.to_hash }
+
+ it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
it { is_expected.to include('CI_COMMIT_REF_NAME' => 'master') }
it { is_expected.to include('CI_PIPELINE_IID' => pipeline.iid.to_s) }
diff --git a/spec/lib/gitlab/ci/build/context/global_spec.rb b/spec/lib/gitlab/ci/build/context/global_spec.rb
index 7394708f9b6..61f2b90426d 100644
--- a/spec/lib/gitlab/ci/build/context/global_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/global_spec.rb
@@ -9,7 +9,9 @@ RSpec.describe Gitlab::Ci::Build::Context::Global do
let(:context) { described_class.new(pipeline, yaml_variables: yaml_variables) }
describe '#variables' do
- subject { context.variables }
+ subject { context.variables.to_hash }
+
+ it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
it { is_expected.to include('CI_COMMIT_REF_NAME' => 'master') }
it { is_expected.to include('CI_PIPELINE_IID' => pipeline.iid.to_s) }
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index f692aa6146e..6c8c968dc0c 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Gitlab::Ci::Build::Policy::Variables do
let(:seed) do
double('build seed',
to_resource: ci_build,
- variables: ci_build.scoped_variables_hash
+ variables: ci_build.scoped_variables
)
end
@@ -91,7 +91,7 @@ RSpec.describe Gitlab::Ci::Build::Policy::Variables do
let(:seed) do
double('bridge seed',
to_resource: bridge,
- variables: ci_build.scoped_variables_hash
+ variables: ci_build.scoped_variables
)
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
index 5694cd5d0a0..6f3c9278677 100644
--- a/spec/lib/gitlab/ci/build/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do
let(:seed) do
double('build seed',
to_resource: ci_build,
- variables: ci_build.scoped_variables_hash
+ variables: ci_build.scoped_variables
)
end
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
index 0b50def05d4..1d5bdf30278 100644
--- a/spec/lib/gitlab/ci/build/rules_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
let(:seed) do
double('build seed',
to_resource: ci_build,
- variables: ci_build.scoped_variables_hash
+ variables: ci_build.scoped_variables
)
end
diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index 46d7d4a58f0..3a82d058819 100644
--- a/spec/lib/gitlab/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -98,7 +98,12 @@ RSpec.describe Gitlab::Ci::Charts do
subject { chart.total }
before do
- create(:ci_empty_pipeline, project: project, duration: 120)
+ # The created_at time used by the following execution
+ # can end up being after the creation of the 'today' time
+ # objects created above, and cause the queried counts to
+ # go to zero when the test executes close to midnight on the
+ # CI system, so we explicitly set it to a day earlier
+ create(:ci_empty_pipeline, project: project, duration: 120, created_at: today - 1.day)
end
it 'uses a utc time zone for range times' do
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index b3b7901074a..179578fe0a8 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -244,6 +244,52 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
end
end
end
+
+ context 'when bridge config contains parallel' do
+ let(:config) { { trigger: 'some/project', parallel: parallel_config } }
+
+ context 'when parallel config is a number' do
+ let(:parallel_config) { 2 }
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns an error message' do
+ expect(subject.errors)
+ .to include(/cannot use "parallel: <number>"/)
+ end
+ end
+ end
+
+ context 'when parallel config is a matrix' do
+ let(:parallel_config) do
+ { matrix: [{ PROVIDER: 'aws', STACK: %w[monitoring app1] },
+ { PROVIDER: 'gcp', STACK: %w[data] }] }
+ end
+
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
+
+ describe '#value' do
+ it 'is returns a bridge job configuration' do
+ expect(subject.value).to eq(
+ name: :my_bridge,
+ trigger: { project: 'some/project' },
+ ignore: false,
+ stage: 'test',
+ only: { refs: %w[branches tags] },
+ parallel: { matrix: [{ 'PROVIDER' => ['aws'], 'STACK' => %w(monitoring app1) },
+ { 'PROVIDER' => ['gcp'], 'STACK' => %w(data) }] },
+ variables: {},
+ scheduling_type: :stage
+ )
+ end
+ end
+ end
+ end
end
describe '#manual_action?' do
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 247f4b63910..064990667d5 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -7,225 +7,285 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
subject(:entry) { described_class.new(config) }
- describe 'validations' do
+ context 'with multiple caches' do
before do
entry.compose!
end
- context 'when entry config value is correct' do
- let(:policy) { nil }
- let(:key) { 'some key' }
- let(:when_config) { nil }
-
- let(:config) do
- {
- key: key,
- untracked: true,
- paths: ['some/path/']
- }.tap do |config|
- config[:policy] = policy if policy
- config[:when] = when_config if when_config
+ describe '#valid?' do
+ context 'when configuration is valid with a single cache' do
+ let(:config) { { key: 'key', paths: ["logs/"], untracked: true } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
end
end
- describe '#value' do
- shared_examples 'hash key value' do
- it 'returns hash value' do
- expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success')
- end
+ context 'when configuration is valid with multiple caches' do
+ let(:config) do
+ [
+ { key: 'key', paths: ["logs/"], untracked: true },
+ { key: 'key2', paths: ["logs/"], untracked: true },
+ { key: 'key3', paths: ["logs/"], untracked: true }
+ ]
end
- it_behaves_like 'hash key value'
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
- context 'with files' do
- let(:key) { { files: %w[a-file other-file] } }
+ context 'when configuration is not a Hash or Array' do
+ let(:config) { 'invalid' }
- it_behaves_like 'hash key value'
+ it 'is invalid' do
+ expect(entry).not_to be_valid
end
+ end
- context 'with files and prefix' do
- let(:key) { { files: %w[a-file other-file], prefix: 'prefix-value' } }
+ context 'when entry values contain more than four caches' do
+ let(:config) do
+ [
+ { key: 'key', paths: ["logs/"], untracked: true },
+ { key: 'key2', paths: ["logs/"], untracked: true },
+ { key: 'key3', paths: ["logs/"], untracked: true },
+ { key: 'key4', paths: ["logs/"], untracked: true },
+ { key: 'key5', paths: ["logs/"], untracked: true }
+ ]
+ end
- it_behaves_like 'hash key value'
+ it 'is invalid' do
+ expect(entry.errors).to eq(["caches config no more than 4 caches can be created"])
+ expect(entry).not_to be_valid
end
+ end
+ end
+ end
+
+ context 'with a single cache' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ end
+ describe 'validations' do
+ before do
+ entry.compose!
+ end
- context 'with prefix' do
- let(:key) { { prefix: 'prefix-value' } }
+ context 'when entry config value is correct' do
+ let(:policy) { nil }
+ let(:key) { 'some key' }
+ let(:when_config) { nil }
- it 'key is nil' do
- expect(entry.value).to match(a_hash_including(key: nil))
+ let(:config) do
+ {
+ key: key,
+ untracked: true,
+ paths: ['some/path/']
+ }.tap do |config|
+ config[:policy] = policy if policy
+ config[:when] = when_config if when_config
end
end
- context 'with `policy`' do
- where(:policy, :result) do
- 'pull-push' | 'pull-push'
- 'push' | 'push'
- 'pull' | 'pull'
- 'unknown' | 'unknown' # invalid
+ describe '#value' do
+ shared_examples 'hash key value' do
+ it 'returns hash value' do
+ expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success')
+ end
end
- with_them do
- it { expect(entry.value).to include(policy: result) }
+ it_behaves_like 'hash key value'
+
+ context 'with files' do
+ let(:key) { { files: %w[a-file other-file] } }
+
+ it_behaves_like 'hash key value'
end
- end
- context 'without `policy`' do
- it 'assigns policy to default' do
- expect(entry.value).to include(policy: 'pull-push')
+ context 'with files and prefix' do
+ let(:key) { { files: %w[a-file other-file], prefix: 'prefix-value' } }
+
+ it_behaves_like 'hash key value'
end
- end
- context 'with `when`' do
- where(:when_config, :result) do
- 'on_success' | 'on_success'
- 'on_failure' | 'on_failure'
- 'always' | 'always'
- 'unknown' | 'unknown' # invalid
+ context 'with prefix' do
+ let(:key) { { prefix: 'prefix-value' } }
+
+ it 'key is nil' do
+ expect(entry.value).to match(a_hash_including(key: nil))
+ end
end
- with_them do
- it { expect(entry.value).to include(when: result) }
+ context 'with `policy`' do
+ where(:policy, :result) do
+ 'pull-push' | 'pull-push'
+ 'push' | 'push'
+ 'pull' | 'pull'
+ 'unknown' | 'unknown' # invalid
+ end
+
+ with_them do
+ it { expect(entry.value).to include(policy: result) }
+ end
end
- end
- context 'without `when`' do
- it 'assigns when to default' do
- expect(entry.value).to include(when: 'on_success')
+ context 'without `policy`' do
+ it 'assigns policy to default' do
+ expect(entry.value).to include(policy: 'pull-push')
+ end
end
- end
- end
- describe '#valid?' do
- it { is_expected.to be_valid }
+ context 'with `when`' do
+ where(:when_config, :result) do
+ 'on_success' | 'on_success'
+ 'on_failure' | 'on_failure'
+ 'always' | 'always'
+ 'unknown' | 'unknown' # invalid
+ end
- context 'with files' do
- let(:key) { { files: %w[a-file other-file] } }
+ with_them do
+ it { expect(entry.value).to include(when: result) }
+ end
+ end
- it { is_expected.to be_valid }
+ context 'without `when`' do
+ it 'assigns when to default' do
+ expect(entry.value).to include(when: 'on_success')
+ end
+ end
end
- end
- context 'with `policy`' do
- where(:policy, :valid) do
- 'pull-push' | true
- 'push' | true
- 'pull' | true
- 'unknown' | false
- end
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+
+ context 'with files' do
+ let(:key) { { files: %w[a-file other-file] } }
- with_them do
- it 'returns expected validity' do
- expect(entry.valid?).to eq(valid)
+ it { is_expected.to be_valid }
end
end
- end
- context 'with `when`' do
- where(:when_config, :valid) do
- 'on_success' | true
- 'on_failure' | true
- 'always' | true
- 'unknown' | false
- end
+ context 'with `policy`' do
+ where(:policy, :valid) do
+ 'pull-push' | true
+ 'push' | true
+ 'pull' | true
+ 'unknown' | false
+ end
- with_them do
- it 'returns expected validity' do
- expect(entry.valid?).to eq(valid)
+ with_them do
+ it 'returns expected validity' do
+ expect(entry.valid?).to eq(valid)
+ end
end
end
- end
- context 'with key missing' do
- let(:config) do
- { untracked: true,
- paths: ['some/path/'] }
+ context 'with `when`' do
+ where(:when_config, :valid) do
+ 'on_success' | true
+ 'on_failure' | true
+ 'always' | true
+ 'unknown' | false
+ end
+
+ with_them do
+ it 'returns expected validity' do
+ expect(entry.valid?).to eq(valid)
+ end
+ end
end
- describe '#value' do
- it 'sets key with the default' do
- expect(entry.value[:key])
- .to eq(Gitlab::Ci::Config::Entry::Key.default)
+ context 'with key missing' do
+ let(:config) do
+ { untracked: true,
+ paths: ['some/path/'] }
+ end
+
+ describe '#value' do
+ it 'sets key with the default' do
+ expect(entry.value[:key])
+ .to eq(Gitlab::Ci::Config::Entry::Key.default)
+ end
end
end
end
- end
- context 'when entry value is not correct' do
- describe '#errors' do
- subject { entry.errors }
+ context 'when entry value is not correct' do
+ describe '#errors' do
+ subject { entry.errors }
- context 'when is not a hash' do
- let(:config) { 'ls' }
+ context 'when is not a hash' do
+ let(:config) { 'ls' }
- it 'reports errors with config value' do
- is_expected.to include 'cache config should be a hash'
+ it 'reports errors with config value' do
+ is_expected.to include 'cache config should be a hash'
+ end
end
- end
- context 'when policy is unknown' do
- let(:config) { { policy: 'unknown' } }
+ context 'when policy is unknown' do
+ let(:config) { { policy: 'unknown' } }
- it 'reports error' do
- is_expected.to include('cache policy should be pull-push, push, or pull')
+ it 'reports error' do
+ is_expected.to include('cache policy should be pull-push, push, or pull')
+ end
end
- end
- context 'when `when` is unknown' do
- let(:config) { { when: 'unknown' } }
+ context 'when `when` is unknown' do
+ let(:config) { { when: 'unknown' } }
- it 'reports error' do
- is_expected.to include('cache when should be on_success, on_failure or always')
+ it 'reports error' do
+ is_expected.to include('cache when should be on_success, on_failure or always')
+ end
end
- end
- context 'when descendants are invalid' do
- context 'with invalid keys' do
- let(:config) { { key: 1 } }
+ context 'when descendants are invalid' do
+ context 'with invalid keys' do
+ let(:config) { { key: 1 } }
- it 'reports error with descendants' do
- is_expected.to include 'key should be a hash, a string or a symbol'
+ it 'reports error with descendants' do
+ is_expected.to include 'key should be a hash, a string or a symbol'
+ end
end
- end
- context 'with empty key' do
- let(:config) { { key: {} } }
+ context 'with empty key' do
+ let(:config) { { key: {} } }
- it 'reports error with descendants' do
- is_expected.to include 'key config missing required keys: files'
+ it 'reports error with descendants' do
+ is_expected.to include 'key config missing required keys: files'
+ end
end
- end
- context 'with invalid files' do
- let(:config) { { key: { files: 'a-file' } } }
+ context 'with invalid files' do
+ let(:config) { { key: { files: 'a-file' } } }
- it 'reports error with descendants' do
- is_expected.to include 'key:files config should be an array of strings'
+ it 'reports error with descendants' do
+ is_expected.to include 'key:files config should be an array of strings'
+ end
end
- end
- context 'with prefix without files' do
- let(:config) { { key: { prefix: 'a-prefix' } } }
+ context 'with prefix without files' do
+ let(:config) { { key: { prefix: 'a-prefix' } } }
- it 'reports error with descendants' do
- is_expected.to include 'key config missing required keys: files'
+ it 'reports error with descendants' do
+ is_expected.to include 'key config missing required keys: files'
+ end
end
- end
- context 'when there is an unknown key present' do
- let(:config) { { key: { unknown: 'a-file' } } }
+ context 'when there is an unknown key present' do
+ let(:config) { { key: { unknown: 'a-file' } } }
- it 'reports error with descendants' do
- is_expected.to include 'key config contains unknown keys: unknown'
+ it 'reports error with descendants' do
+ is_expected.to include 'key config contains unknown keys: unknown'
+ end
end
end
- end
- context 'when there is an unknown key present' do
- let(:config) { { invalid: true } }
+ context 'when there is an unknown key present' do
+ let(:config) { { invalid: true } }
- it 'reports error with descendants' do
- is_expected.to include 'cache config contains unknown keys: invalid'
+ it 'reports error with descendants' do
+ is_expected.to include 'cache config contains unknown keys: invalid'
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 0c18a7fb71e..dd8a79f0d84 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -305,4 +305,37 @@ RSpec.describe Gitlab::Ci::Config::Entry::Environment do
it { expect(entry).to be_valid }
end
end
+
+ describe 'deployment_tier' do
+ let(:config) do
+ { name: 'customer-portal', deployment_tier: deployment_tier }
+ end
+
+ context 'is a string' do
+ let(:deployment_tier) { 'production' }
+
+ it { expect(entry).to be_valid }
+ end
+
+ context 'is a hash' do
+ let(:deployment_tier) { Hash(tier: 'production') }
+
+ it { expect(entry).not_to be_valid }
+ end
+
+ context 'is nil' do
+ let(:deployment_tier) { nil }
+
+ it { expect(entry).to be_valid }
+ end
+
+ context 'is unknown value' do
+ let(:deployment_tier) { 'unknown' }
+
+ it 'is invalid and adds an error' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include("environment deployment tier must be one of #{::Environment.tiers.keys.join(', ')}")
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index a3b5f32b9f9..a4167003987 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -537,7 +537,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'overrides default config' do
expect(entry[:image].value).to eq(name: 'some_image')
- expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
+ expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success'])
end
end
@@ -552,7 +552,43 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'uses config from default entry' do
expect(entry[:image].value).to eq 'specified'
- expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
+ expect(entry[:cache].value).to eq([key: 'test', policy: 'pull-push', when: 'on_success'])
+ end
+ end
+
+ context 'with multiple_cache_per_job FF disabled' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ end
+
+ context 'when job config overrides default config' do
+ before do
+ entry.compose!(deps)
+ end
+
+ let(:config) do
+ { script: 'rspec', image: 'some_image', cache: { key: 'test' } }
+ end
+
+ it 'overrides default config' do
+ expect(entry[:image].value).to eq(name: 'some_image')
+ expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
+ end
+ end
+
+ context 'when job config does not override default config' do
+ before do
+ allow(default).to receive('[]').with(:image).and_return(specified)
+
+ entry.compose!(deps)
+ end
+
+ let(:config) { { script: 'ls', cache: { key: 'test' } } }
+
+ it 'uses config from default entry' do
+ expect(entry[:image].value).to eq 'specified'
+ expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success')
+ 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 983e95fae42..a0a5dd52ad4 100644
--- a/spec/lib/gitlab/ci/config/entry/need_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb
@@ -23,7 +23,17 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
describe '#value' do
it 'returns job needs configuration' do
- expect(need.value).to eq(name: 'job_name', artifacts: true)
+ expect(need.value).to eq(name: 'job_name', artifacts: true, optional: false)
+ end
+
+ context 'when the FF ci_needs_optional is disabled' do
+ before do
+ stub_feature_flags(ci_needs_optional: false)
+ end
+
+ it 'returns job needs configuration without `optional`' do
+ expect(need.value).to eq(name: 'job_name', artifacts: true)
+ end
end
end
@@ -58,7 +68,7 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
describe '#value' do
it 'returns job needs configuration' do
- expect(need.value).to eq(name: 'job_name', artifacts: true)
+ expect(need.value).to eq(name: 'job_name', artifacts: true, optional: false)
end
end
@@ -74,7 +84,7 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
describe '#value' do
it 'returns job needs configuration' do
- expect(need.value).to eq(name: 'job_name', artifacts: false)
+ expect(need.value).to eq(name: 'job_name', artifacts: false, optional: false)
end
end
@@ -90,7 +100,7 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
describe '#value' do
it 'returns job needs configuration' do
- expect(need.value).to eq(name: 'job_name', artifacts: true)
+ expect(need.value).to eq(name: 'job_name', artifacts: true, optional: false)
end
end
@@ -106,11 +116,77 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
describe '#value' do
it 'returns job needs configuration' do
- expect(need.value).to eq(name: 'job_name', artifacts: true)
+ expect(need.value).to eq(name: 'job_name', artifacts: true, optional: false)
+ end
+ end
+
+ it_behaves_like 'job type'
+ end
+
+ context 'with job name and optional true' do
+ let(:config) { { job: 'job_name', optional: true } }
+
+ it { is_expected.to be_valid }
+
+ it_behaves_like 'job type'
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(name: 'job_name', artifacts: true, optional: true)
+ end
+
+ context 'when the FF ci_needs_optional is disabled' do
+ before do
+ stub_feature_flags(ci_needs_optional: false)
+ end
+
+ it 'returns job needs configuration without `optional`' do
+ expect(need.value).to eq(name: 'job_name', artifacts: true)
+ end
end
end
+ end
+
+ context 'with job name and optional false' do
+ let(:config) { { job: 'job_name', optional: false } }
+
+ it { is_expected.to be_valid }
it_behaves_like 'job type'
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(name: 'job_name', artifacts: true, optional: false)
+ end
+ end
+ end
+
+ context 'with job name and optional nil' do
+ let(:config) { { job: 'job_name', optional: nil } }
+
+ it { is_expected.to be_valid }
+
+ it_behaves_like 'job type'
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(name: 'job_name', artifacts: true, optional: false)
+ end
+ end
+ end
+
+ context 'without optional key' do
+ let(:config) { { job: 'job_name' } }
+
+ it { is_expected.to be_valid }
+
+ it_behaves_like 'job type'
+
+ describe '#value' do
+ it 'returns job needs configuration' do
+ expect(need.value).to eq(name: 'job_name', artifacts: true, optional: false)
+ end
+ end
end
context 'when job name is empty' do
diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
index f11f2a56f5f..489fbac68b2 100644
--- a/spec/lib/gitlab/ci/config/entry/needs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
@@ -111,8 +111,8 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
it 'returns key value' do
expect(needs.value).to eq(
job: [
- { name: 'first_job_name', artifacts: true },
- { name: 'second_job_name', artifacts: true }
+ { name: 'first_job_name', artifacts: true, optional: false },
+ { name: 'second_job_name', artifacts: true, optional: false }
]
)
end
@@ -124,8 +124,8 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
context 'with complex job entries composed' do
let(:config) do
[
- { job: 'first_job_name', artifacts: true },
- { job: 'second_job_name', artifacts: false }
+ { job: 'first_job_name', artifacts: true, optional: false },
+ { job: 'second_job_name', artifacts: false, optional: false }
]
end
@@ -137,8 +137,8 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
it 'returns key value' do
expect(needs.value).to eq(
job: [
- { name: 'first_job_name', artifacts: true },
- { name: 'second_job_name', artifacts: false }
+ { name: 'first_job_name', artifacts: true, optional: false },
+ { name: 'second_job_name', artifacts: false, optional: false }
]
)
end
@@ -163,8 +163,8 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
it 'returns key value' do
expect(needs.value).to eq(
job: [
- { name: 'first_job_name', artifacts: true },
- { name: 'second_job_name', artifacts: false }
+ { name: 'first_job_name', artifacts: true, optional: false },
+ { name: 'second_job_name', artifacts: false, optional: false }
]
)
end
diff --git a/spec/lib/gitlab/ci/config/entry/product/parallel_spec.rb b/spec/lib/gitlab/ci/config/entry/product/parallel_spec.rb
index bc09e20d748..937642f07e7 100644
--- a/spec/lib/gitlab/ci/config/entry/product/parallel_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/product/parallel_spec.rb
@@ -4,21 +4,23 @@ require 'fast_spec_helper'
require_dependency 'active_model'
RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Parallel do
- subject(:parallel) { described_class.new(config) }
+ let(:metadata) { {} }
- context 'with invalid config' do
- shared_examples 'invalid config' do |error_message|
- describe '#valid?' do
- it { is_expected.not_to be_valid }
- end
+ subject(:parallel) { described_class.new(config, **metadata) }
- describe '#errors' do
- it 'returns error about invalid type' do
- expect(parallel.errors).to match(a_collection_including(error_message))
- end
+ shared_examples 'invalid config' do |error_message|
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns error about invalid type' do
+ expect(parallel.errors).to match(a_collection_including(error_message))
end
end
+ end
+ context 'with invalid config' do
context 'when it is not a numeric value' do
let(:config) { true }
@@ -63,6 +65,12 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Parallel do
expect(parallel.value).to match(number: config)
end
end
+
+ context 'when :numeric is not allowed' do
+ let(:metadata) { { allowed_strategies: [:matrix] } }
+
+ it_behaves_like 'invalid config', /cannot use "parallel: <number>"/
+ end
end
end
@@ -89,6 +97,12 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Parallel do
])
end
end
+
+ context 'when :matrix is not allowed' do
+ let(:metadata) { { allowed_strategies: [:numeric] } }
+
+ it_behaves_like 'invalid config', /cannot use "parallel: matrix"/
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 54c7a5c3602..7b38c21788f 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -126,49 +126,105 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
expect(root.jobs_value.keys).to eq([:rspec, :spinach, :release])
expect(root.jobs_value[:rspec]).to eq(
{ name: :rspec,
- script: %w[rspec ls],
- before_script: %w(ls pwd),
- image: { name: 'ruby:2.7' },
- services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
- stage: 'test',
- cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
- variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
- ignore: false,
- after_script: ['make clean'],
- only: { refs: %w[branches tags] },
- scheduling_type: :stage }
+ script: %w[rspec ls],
+ before_script: %w(ls pwd),
+ image: { name: 'ruby:2.7' },
+ services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
+ stage: 'test',
+ cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
+ variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ ignore: false,
+ after_script: ['make clean'],
+ only: { refs: %w[branches tags] },
+ scheduling_type: :stage }
)
expect(root.jobs_value[:spinach]).to eq(
{ name: :spinach,
- before_script: [],
- script: %w[spinach],
- image: { name: 'ruby:2.7' },
- services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
- stage: 'test',
- cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
- variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
- ignore: false,
- after_script: ['make clean'],
- only: { refs: %w[branches tags] },
- scheduling_type: :stage }
+ before_script: [],
+ script: %w[spinach],
+ image: { name: 'ruby:2.7' },
+ services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
+ stage: 'test',
+ cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
+ variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ ignore: false,
+ after_script: ['make clean'],
+ only: { refs: %w[branches tags] },
+ scheduling_type: :stage }
)
expect(root.jobs_value[:release]).to eq(
{ name: :release,
- stage: 'release',
- before_script: [],
- script: ["make changelog | tee release_changelog.txt"],
- release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" },
- image: { name: "ruby:2.7" },
- services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
- cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' },
- only: { refs: %w(branches tags) },
- variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
- after_script: [],
- ignore: false,
- scheduling_type: :stage }
+ stage: 'release',
+ before_script: [],
+ script: ["make changelog | tee release_changelog.txt"],
+ release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" },
+ image: { name: "ruby:2.7" },
+ services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
+ cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }],
+ only: { refs: %w(branches tags) },
+ variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
+ after_script: [],
+ ignore: false,
+ scheduling_type: :stage }
)
end
end
+
+ context 'with multuple_cache_per_job FF disabled' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ root.compose!
+ end
+
+ describe '#jobs_value' do
+ it 'returns jobs configuration' do
+ expect(root.jobs_value.keys).to eq([:rspec, :spinach, :release])
+ expect(root.jobs_value[:rspec]).to eq(
+ { name: :rspec,
+ script: %w[rspec ls],
+ before_script: %w(ls pwd),
+ image: { name: 'ruby:2.7' },
+ services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
+ stage: 'test',
+ cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
+ variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ ignore: false,
+ after_script: ['make clean'],
+ only: { refs: %w[branches tags] },
+ scheduling_type: :stage }
+ )
+ expect(root.jobs_value[:spinach]).to eq(
+ { name: :spinach,
+ before_script: [],
+ script: %w[spinach],
+ image: { name: 'ruby:2.7' },
+ services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
+ stage: 'test',
+ cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
+ variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ ignore: false,
+ after_script: ['make clean'],
+ only: { refs: %w[branches tags] },
+ scheduling_type: :stage }
+ )
+ expect(root.jobs_value[:release]).to eq(
+ { name: :release,
+ stage: 'release',
+ before_script: [],
+ script: ["make changelog | tee release_changelog.txt"],
+ release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" },
+ image: { name: "ruby:2.7" },
+ services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
+ cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' },
+ only: { refs: %w(branches tags) },
+ variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
+ after_script: [],
+ ignore: false,
+ scheduling_type: :stage }
+ )
+ end
+ end
+ end
end
end
@@ -187,6 +243,52 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
spinach: { before_script: [], variables: { VAR: 'job' }, script: 'spinach' } }
end
+ context 'with multiple_cache_per_job FF disabled' do
+ context 'when composed' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ root.compose!
+ end
+
+ describe '#errors' do
+ it 'has no errors' do
+ expect(root.errors).to be_empty
+ end
+ end
+
+ describe '#jobs_value' do
+ it 'returns jobs configuration' do
+ expect(root.jobs_value).to eq(
+ rspec: { name: :rspec,
+ script: %w[rspec ls],
+ before_script: %w(ls pwd),
+ image: { name: 'ruby:2.7' },
+ services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
+ stage: 'test',
+ cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
+ variables: { 'VAR' => 'root' },
+ ignore: false,
+ after_script: ['make clean'],
+ only: { refs: %w[branches tags] },
+ scheduling_type: :stage },
+ spinach: { name: :spinach,
+ before_script: [],
+ script: %w[spinach],
+ image: { name: 'ruby:2.7' },
+ services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
+ stage: 'test',
+ cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
+ variables: { 'VAR' => 'job' },
+ ignore: false,
+ after_script: ['make clean'],
+ only: { refs: %w[branches tags] },
+ scheduling_type: :stage }
+ )
+ end
+ end
+ end
+ end
+
context 'when composed' do
before do
root.compose!
@@ -202,29 +304,29 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
it 'returns jobs configuration' do
expect(root.jobs_value).to eq(
rspec: { name: :rspec,
- script: %w[rspec ls],
- before_script: %w(ls pwd),
- image: { name: 'ruby:2.7' },
- services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
- stage: 'test',
- cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
- variables: { 'VAR' => 'root' },
- ignore: false,
- after_script: ['make clean'],
- only: { refs: %w[branches tags] },
- scheduling_type: :stage },
+ script: %w[rspec ls],
+ before_script: %w(ls pwd),
+ image: { name: 'ruby:2.7' },
+ services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
+ stage: 'test',
+ cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
+ variables: { 'VAR' => 'root' },
+ ignore: false,
+ after_script: ['make clean'],
+ only: { refs: %w[branches tags] },
+ scheduling_type: :stage },
spinach: { name: :spinach,
- before_script: [],
- script: %w[spinach],
- image: { name: 'ruby:2.7' },
- services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
- stage: 'test',
- cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
- variables: { 'VAR' => 'job' },
- ignore: false,
- after_script: ['make clean'],
- only: { refs: %w[branches tags] },
- scheduling_type: :stage }
+ before_script: [],
+ script: %w[spinach],
+ image: { name: 'ruby:2.7' },
+ services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
+ stage: 'test',
+ cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
+ variables: { 'VAR' => 'job' },
+ ignore: false,
+ after_script: ['make clean'],
+ only: { refs: %w[branches tags] },
+ scheduling_type: :stage }
)
end
end
@@ -265,7 +367,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
describe '#cache_value' do
it 'returns correct cache definition' do
- expect(root.cache_value).to eq(key: 'a', policy: 'pull-push', when: 'on_success')
+ expect(root.cache_value).to eq([key: 'a', policy: 'pull-push', when: 'on_success'])
+ end
+ end
+
+ context 'with multiple_cache_per_job FF disabled' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ root.compose!
+ end
+
+ describe '#cache_value' do
+ it 'returns correct cache definition' do
+ expect(root.cache_value).to eq(key: 'a', policy: 'pull-push', when: 'on_success')
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/jwt_spec.rb b/spec/lib/gitlab/ci/jwt_spec.rb
index 342ca6b8b75..480a4a05379 100644
--- a/spec/lib/gitlab/ci/jwt_spec.rb
+++ b/spec/lib/gitlab/ci/jwt_spec.rb
@@ -114,17 +114,6 @@ RSpec.describe Gitlab::Ci::Jwt do
expect(payload[:environment]).to eq('production')
expect(payload[:environment_protected]).to eq('false')
end
-
- context ':ci_jwt_include_environment feature flag is disabled' do
- before do
- stub_feature_flags(ci_jwt_include_environment: false)
- end
-
- it 'does not include environment attributes' do
- expect(payload).not_to have_key(:environment)
- expect(payload).not_to have_key(:environment_protected)
- end
- end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index cf3644c9ad5..ec7eebdc056 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -3,17 +3,16 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do
- subject do
- described_class.new(text, variables)
+ let(:variables) do
+ Gitlab::Ci::Variables::Collection.new
+ .append(key: 'PRESENT_VARIABLE', value: 'my variable')
+ .append(key: 'PATH_VARIABLE', value: 'a/path/variable/value')
+ .append(key: 'FULL_PATH_VARIABLE', value: '/a/full/path/variable/value')
+ .append(key: 'EMPTY_VARIABLE', value: '')
end
- let(:variables) do
- {
- 'PRESENT_VARIABLE' => 'my variable',
- 'PATH_VARIABLE' => 'a/path/variable/value',
- 'FULL_PATH_VARIABLE' => '/a/full/path/variable/value',
- 'EMPTY_VARIABLE' => ''
- }
+ subject do
+ described_class.new(text, variables)
end
describe '.new' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
index 570706bfaac..773cb61b946 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
@@ -9,8 +9,255 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
let(:processor) { described_class.new(pipeline, config) }
- describe '#build_attributes' do
- subject { processor.build_attributes }
+ context 'with multiple_cache_per_job ff disabled' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ end
+
+ describe '#build_attributes' do
+ subject { processor.build_attributes }
+
+ context 'with cache:key' do
+ let(:config) do
+ {
+ key: 'a-key',
+ paths: ['vendor/ruby']
+ }
+ end
+
+ it { is_expected.to include(options: { cache: config }) }
+ end
+
+ context 'with cache:key as a symbol' do
+ let(:config) do
+ {
+ key: :a_key,
+ paths: ['vendor/ruby']
+ }
+ end
+
+ it { is_expected.to include(options: { cache: config.merge(key: "a_key") }) }
+ end
+
+ context 'with cache:key:files' do
+ shared_examples 'default key' do
+ let(:config) do
+ { key: { files: files } }
+ end
+
+ it 'uses default key' do
+ expected = { options: { cache: { key: 'default' } } }
+
+ is_expected.to include(expected)
+ end
+ end
+
+ shared_examples 'version and gemfile files' do
+ let(:config) do
+ {
+ key: {
+ files: files
+ },
+ paths: ['vendor/ruby']
+ }
+ end
+
+ it 'builds a string key' do
+ expected = {
+ options: {
+ cache: {
+ key: '703ecc8fef1635427a1f86a8a1a308831c122392',
+ paths: ['vendor/ruby']
+ }
+ }
+ }
+
+ is_expected.to include(expected)
+ end
+ end
+
+ context 'with existing files' do
+ let(:files) { ['VERSION', 'Gemfile.zip'] }
+
+ it_behaves_like 'version and gemfile files'
+ end
+
+ context 'with files starting with ./' do
+ let(:files) { ['Gemfile.zip', './VERSION'] }
+
+ it_behaves_like 'version and gemfile files'
+ end
+
+ context 'with files ending with /' do
+ let(:files) { ['Gemfile.zip/'] }
+
+ it_behaves_like 'default key'
+ end
+
+ context 'with new line in filenames' do
+ let(:files) { ["Gemfile.zip\nVERSION"] }
+
+ it_behaves_like 'default key'
+ end
+
+ context 'with missing files' do
+ let(:files) { ['project-gemfile.lock', ''] }
+
+ it_behaves_like 'default key'
+ end
+
+ context 'with directories' do
+ shared_examples 'foo/bar directory key' do
+ let(:config) do
+ {
+ key: {
+ files: files
+ }
+ }
+ end
+
+ it 'builds a string key' do
+ expected = {
+ options: {
+ cache: { key: '74bf43fb1090f161bdd4e265802775dbda2f03d1' }
+ }
+ }
+
+ is_expected.to include(expected)
+ end
+ end
+
+ context 'with directory' do
+ let(:files) { ['foo/bar'] }
+
+ it_behaves_like 'foo/bar directory key'
+ end
+
+ context 'with directory ending in slash' do
+ let(:files) { ['foo/bar/'] }
+
+ it_behaves_like 'foo/bar directory key'
+ end
+
+ context 'with directories ending in slash star' do
+ let(:files) { ['foo/bar/*'] }
+
+ it_behaves_like 'foo/bar directory key'
+ end
+ end
+ end
+
+ context 'with cache:key:prefix' do
+ context 'without files' do
+ let(:config) do
+ {
+ key: {
+ prefix: 'a-prefix'
+ },
+ paths: ['vendor/ruby']
+ }
+ end
+
+ it 'adds prefix to default key' do
+ expected = {
+ options: {
+ cache: {
+ key: 'a-prefix-default',
+ paths: ['vendor/ruby']
+ }
+ }
+ }
+
+ is_expected.to include(expected)
+ end
+ end
+
+ context 'with existing files' do
+ let(:config) do
+ {
+ key: {
+ files: ['VERSION', 'Gemfile.zip'],
+ prefix: 'a-prefix'
+ },
+ paths: ['vendor/ruby']
+ }
+ end
+
+ it 'adds prefix key' do
+ expected = {
+ options: {
+ cache: {
+ key: 'a-prefix-703ecc8fef1635427a1f86a8a1a308831c122392',
+ paths: ['vendor/ruby']
+ }
+ }
+ }
+
+ is_expected.to include(expected)
+ end
+ end
+
+ context 'with missing files' do
+ let(:config) do
+ {
+ key: {
+ files: ['project-gemfile.lock', ''],
+ prefix: 'a-prefix'
+ },
+ paths: ['vendor/ruby']
+ }
+ end
+
+ it 'adds prefix to default key' do
+ expected = {
+ options: {
+ cache: {
+ key: 'a-prefix-default',
+ paths: ['vendor/ruby']
+ }
+ }
+ }
+
+ is_expected.to include(expected)
+ end
+ end
+ end
+
+ context 'with all cache option keys' do
+ let(:config) do
+ {
+ key: 'a-key',
+ paths: ['vendor/ruby'],
+ untracked: true,
+ policy: 'push',
+ when: 'on_success'
+ }
+ end
+
+ it { is_expected.to include(options: { cache: config }) }
+ end
+
+ context 'with unknown cache option keys' do
+ let(:config) do
+ {
+ key: 'a-key',
+ unknown_key: true
+ }
+ end
+
+ it { expect { subject }.to raise_error(ArgumentError, /unknown_key/) }
+ end
+
+ context 'with empty config' do
+ let(:config) { {} }
+
+ it { is_expected.to include(options: {}) }
+ end
+ end
+ end
+
+ describe '#attributes' do
+ subject { processor.attributes }
context 'with cache:key' do
let(:config) do
@@ -20,7 +267,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
}
end
- it { is_expected.to include(options: { cache: config }) }
+ it { is_expected.to include(config) }
end
context 'with cache:key as a symbol' do
@@ -31,7 +278,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
}
end
- it { is_expected.to include(options: { cache: config.merge(key: "a_key") }) }
+ it { is_expected.to include(config.merge(key: "a_key")) }
end
context 'with cache:key:files' do
@@ -41,7 +288,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
end
it 'uses default key' do
- expected = { options: { cache: { key: 'default' } } }
+ expected = { key: 'default' }
is_expected.to include(expected)
end
@@ -59,13 +306,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it 'builds a string key' do
expected = {
- options: {
- cache: {
key: '703ecc8fef1635427a1f86a8a1a308831c122392',
paths: ['vendor/ruby']
- }
}
- }
is_expected.to include(expected)
end
@@ -112,11 +355,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
end
it 'builds a string key' do
- expected = {
- options: {
- cache: { key: '74bf43fb1090f161bdd4e265802775dbda2f03d1' }
- }
- }
+ expected = { key: '74bf43fb1090f161bdd4e265802775dbda2f03d1' }
is_expected.to include(expected)
end
@@ -155,13 +394,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it 'adds prefix to default key' do
expected = {
- options: {
- cache: {
key: 'a-prefix-default',
paths: ['vendor/ruby']
}
- }
- }
is_expected.to include(expected)
end
@@ -180,13 +415,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it 'adds prefix key' do
expected = {
- options: {
- cache: {
key: 'a-prefix-703ecc8fef1635427a1f86a8a1a308831c122392',
paths: ['vendor/ruby']
}
- }
- }
is_expected.to include(expected)
end
@@ -205,13 +436,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it 'adds prefix to default key' do
expected = {
- options: {
- cache: {
key: 'a-prefix-default',
paths: ['vendor/ruby']
}
- }
- }
is_expected.to include(expected)
end
@@ -229,7 +456,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
}
end
- it { is_expected.to include(options: { cache: config }) }
+ it { is_expected.to include(config) }
end
context 'with unknown cache option keys' do
@@ -242,11 +469,5 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
it { expect { subject }.to raise_error(ArgumentError, /unknown_key/) }
end
-
- context 'with empty config' do
- let(:config) { {} }
-
- it { is_expected.to include(options: {}) }
- end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 0efc7484699..7ec6949f852 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -85,99 +85,169 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
{ key: 'VAR2', value: 'var 2', public: true },
{ key: 'VAR3', value: 'var 3', public: true }])
end
+ end
- context 'when FF ci_rules_variables is disabled' do
- before do
- stub_feature_flags(ci_rules_variables: false)
- end
+ context 'with multiple_cache_per_job FF disabled' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ end
- it do
- is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
- { key: 'VAR2', value: 'var 2', public: true }])
+ context 'with cache:key' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: {
+ key: 'a-value'
+ }
+ }
end
+
+ it { is_expected.to include(options: { cache: { key: 'a-value' } }) }
end
- end
- context 'with cache:key' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: {
- key: 'a-value'
+ context 'with cache:key:files' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: {
+ key: {
+ files: ['VERSION']
+ }
+ }
}
- }
- end
+ end
- it { is_expected.to include(options: { cache: { key: 'a-value' } }) }
- end
+ it 'includes cache options' do
+ cache_options = {
+ options: {
+ cache: { key: 'f155568ad0933d8358f66b846133614f76dd0ca4' }
+ }
+ }
- context 'with cache:key:files' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: {
- key: {
- files: ['VERSION']
+ is_expected.to include(cache_options)
+ end
+ end
+
+ context 'with cache:key:prefix' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: {
+ key: {
+ prefix: 'something'
+ }
}
}
- }
+ end
+
+ it { is_expected.to include(options: { cache: { key: 'something-default' } }) }
end
- it 'includes cache options' do
- cache_options = {
- options: {
+ context 'with cache:key:files and prefix' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
cache: {
- key: 'f155568ad0933d8358f66b846133614f76dd0ca4'
+ key: {
+ files: ['VERSION'],
+ prefix: 'something'
+ }
}
}
- }
+ end
- is_expected.to include(cache_options)
+ it 'includes cache options' do
+ cache_options = {
+ options: {
+ cache: { key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4' }
+ }
+ }
+
+ is_expected.to include(cache_options)
+ end
end
end
- context 'with cache:key:prefix' do
+ context 'with cache:key' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
- cache: {
- key: {
- prefix: 'something'
- }
- }
+ cache: [{
+ key: 'a-value'
+ }]
}
end
- it { is_expected.to include(options: { cache: { key: 'something-default' } }) }
- end
+ it { is_expected.to include(options: { cache: [a_hash_including(key: 'a-value')] }) }
- context 'with cache:key:files and prefix' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: {
- key: {
- files: ['VERSION'],
- prefix: 'something'
+ context 'with cache:key:files' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: [{
+ key: {
+ files: ['VERSION']
+ }
+ }]
+ }
+ end
+
+ it 'includes cache options' do
+ cache_options = {
+ options: {
+ cache: [a_hash_including(key: 'f155568ad0933d8358f66b846133614f76dd0ca4')]
}
}
- }
+
+ is_expected.to include(cache_options)
+ end
end
- it 'includes cache options' do
- cache_options = {
- options: {
- cache: {
- key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4'
+ context 'with cache:key:prefix' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: [{
+ key: {
+ prefix: 'something'
+ }
+ }]
+ }
+ end
+
+ it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) }
+ end
+
+ context 'with cache:key:files and prefix' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: [{
+ key: {
+ files: ['VERSION'],
+ prefix: 'something'
+ }
+ }]
+ }
+ end
+
+ it 'includes cache options' do
+ cache_options = {
+ options: {
+ cache: [a_hash_including(key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4')]
}
}
- }
- is_expected.to include(cache_options)
+ is_expected.to include(cache_options)
+ end
end
end
@@ -190,7 +260,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
}
end
- it { is_expected.to include(options: {}) }
+ it { is_expected.to include({}) }
end
context 'with allow_failure' do
@@ -307,7 +377,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it 'does not have environment' do
expect(subject).not_to be_has_environment
expect(subject.environment).to be_nil
- expect(subject.metadata.expanded_environment_name).to be_nil
+ expect(subject.metadata).to be_nil
expect(Environment.exists?(name: expected_environment_name)).to eq(false)
end
end
@@ -979,6 +1049,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
expect(subject.errors).to contain_exactly(
"'rspec' job needs 'build' job, but it was not added to the pipeline")
end
+
+ context 'when the needed job is optional' do
+ let(:needs_attributes) { [{ name: 'build', optional: true }] }
+
+ it "does not return an error" do
+ expect(subject.errors).to be_empty
+ end
+
+ context 'when the FF ci_needs_optional is disabled' do
+ before do
+ stub_feature_flags(ci_needs_optional: false)
+ end
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "'rspec' job needs 'build' job, but it was not added to the pipeline")
+ end
+ end
+ end
end
context 'when build job is part of prior stages' do
@@ -1036,4 +1125,75 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
end
+
+ describe 'applying pipeline variables' do
+ subject { seed_build }
+
+ let(:pipeline_variables) { [] }
+ let(:pipeline) do
+ build(:ci_empty_pipeline, project: project, sha: head_sha, variables: pipeline_variables)
+ end
+
+ context 'containing variable references' do
+ let(:pipeline_variables) do
+ [
+ build(:ci_pipeline_variable, key: 'A', value: '$B'),
+ build(:ci_pipeline_variable, key: 'B', value: '$C')
+ ]
+ end
+
+ context 'when FF :variable_inside_variable is enabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: [project])
+ end
+
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
+ end
+ end
+ end
+
+ context 'containing cyclic reference' do
+ let(:pipeline_variables) do
+ [
+ build(:ci_pipeline_variable, key: 'A', value: '$B'),
+ build(:ci_pipeline_variable, key: 'B', value: '$C'),
+ build(:ci_pipeline_variable, key: 'C', value: '$A')
+ ]
+ end
+
+ context 'when FF :variable_inside_variable is disabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: false)
+ end
+
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
+ end
+ end
+
+ context 'when FF :variable_inside_variable is enabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: [project])
+ end
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ 'rspec: circular variable reference detected: ["A", "B", "C"]')
+ end
+
+ context 'with job:rules:[if:]' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$C != null', when: 'always' }] } }
+
+ it "included? does not raise" do
+ expect { subject.included? }.not_to raise_error
+ end
+
+ it "included? returns true" do
+ expect(subject.included?).to eq(true)
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
index 664aaaedf7b..99196d393c6 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
@@ -88,6 +88,55 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do
end
end
+ context 'when job has deployment tier attribute' do
+ let(:attributes) do
+ {
+ environment: 'customer-portal',
+ options: {
+ environment: {
+ name: 'customer-portal',
+ deployment_tier: deployment_tier
+ }
+ }
+ }
+ end
+
+ let(:deployment_tier) { 'production' }
+
+ context 'when environment has not been created yet' do
+ it 'sets the specified deployment tier' do
+ is_expected.to be_production
+ end
+
+ context 'when deployment tier is staging' do
+ let(:deployment_tier) { 'staging' }
+
+ it 'sets the specified deployment tier' do
+ is_expected.to be_staging
+ end
+ end
+
+ context 'when deployment tier is unknown' do
+ let(:deployment_tier) { 'unknown' }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError, "'unknown' is not a valid tier")
+ end
+ end
+ end
+
+ context 'when environment has already been created' do
+ before do
+ create(:environment, :staging, project: project, name: 'customer-portal')
+ end
+
+ it 'does not overwrite the specified deployment tier' do
+ # This is to be updated when a deployment succeeded i.e. Deployments::UpdateEnvironmentService.
+ is_expected.to be_staging
+ end
+ end
+ end
+
context 'when job starts a review app' do
let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' }
let(:expected_environment_name) { "review/#{job.ref}" }
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
index 90188b56f5a..b322e55cb5a 100644
--- a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
@@ -27,6 +27,22 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(report_status).to eq(described_class::STATUS_SUCCESS)
end
end
+
+ context 'when head report does not exist' do
+ let(:head_report) { nil }
+
+ it 'returns status not found' do
+ expect(report_status).to eq(described_class::STATUS_NOT_FOUND)
+ end
+ end
+
+ context 'when base report does not exist' do
+ let(:base_report) { nil }
+
+ it 'returns status success' do
+ expect(report_status).to eq(described_class::STATUS_NOT_FOUND)
+ end
+ end
end
describe '#errors_count' do
@@ -93,6 +109,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(resolved_count).to be_zero
end
end
+
+ context 'when base report is nil' do
+ let(:base_report) { nil }
+
+ it 'returns zero' do
+ expect(resolved_count).to be_zero
+ end
+ end
end
describe '#total_count' do
@@ -140,6 +164,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(total_count).to eq(2)
end
end
+
+ context 'when base report is nil' do
+ let(:base_report) { nil }
+
+ it 'returns zero' do
+ expect(total_count).to be_zero
+ end
+ end
end
describe '#existing_errors' do
@@ -177,6 +209,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(existing_errors).to be_empty
end
end
+
+ context 'when base report is nil' do
+ let(:base_report) { nil }
+
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
+ end
+ end
end
describe '#new_errors' do
@@ -213,6 +253,14 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(new_errors).to eq([degradation_1])
end
end
+
+ context 'when base report is nil' do
+ let(:base_report) { nil }
+
+ it 'returns an empty array' do
+ expect(new_errors).to be_empty
+ end
+ end
end
describe '#resolved_errors' do
@@ -250,5 +298,13 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
expect(resolved_errors).to be_empty
end
end
+
+ context 'when base report is nil' do
+ let(:base_report) { nil }
+
+ it 'returns an empty array' do
+ expect(resolved_errors).to be_empty
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb
index 1e5e4766583..7ed9270e9a0 100644
--- a/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb
@@ -45,6 +45,22 @@ RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
expect(status).to eq('failed')
end
end
+
+ context 'when base_report is nil' do
+ let(:base_report) { nil }
+
+ it 'returns status not_found' do
+ expect(status).to eq('not_found')
+ end
+ end
+
+ context 'when head_report is nil' do
+ let(:head_report) { nil }
+
+ it 'returns status not_found' do
+ expect(status).to eq('not_found')
+ end
+ end
end
describe '#success?' do
@@ -94,4 +110,22 @@ RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
expect { total_count }.to raise_error(NotImplementedError)
end
end
+
+ describe '#not_found?' do
+ subject(:not_found) { comparer.not_found? }
+
+ context 'when base report is nil' do
+ let(:base_report) { nil }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when base report exists' do
+ before do
+ allow(comparer).to receive(:success?).and_return(true)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
index a98d3db4e82..9acea852832 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
@@ -87,12 +87,44 @@ RSpec.describe Gitlab::Ci::Reports::TestSuiteSummary do
end
end
+ describe '#suite_error' do
+ subject(:suite_error) { test_suite_summary.suite_error }
+
+ context 'when there are no build report results with suite errors' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'when there are build report results with suite errors' do
+ let(:build_report_result_1) do
+ build(
+ :ci_build_report_result,
+ :with_junit_suite_error,
+ test_suite_name: 'karma',
+ test_suite_error: 'karma parsing error'
+ )
+ end
+
+ let(:build_report_result_2) do
+ build(
+ :ci_build_report_result,
+ :with_junit_suite_error,
+ test_suite_name: 'karma',
+ test_suite_error: 'another karma parsing error'
+ )
+ end
+
+ it 'includes the first suite error from the collection of build report results' do
+ expect(suite_error).to eq('karma parsing error')
+ end
+ end
+ end
+
describe '#to_h' do
subject { test_suite_summary.to_h }
context 'when test suite summary has several build report results' do
it 'returns the total as a hash' do
- expect(subject).to include(:time, :count, :success, :failed, :skipped, :error)
+ expect(subject).to include(:time, :count, :success, :failed, :skipped, :error, :suite_error)
end
end
end
diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb
index bcfb9f19792..543cfe874ca 100644
--- a/spec/lib/gitlab/ci/status/composite_spec.rb
+++ b/spec/lib/gitlab/ci/status/composite_spec.rb
@@ -69,6 +69,8 @@ RSpec.describe Gitlab::Ci::Status::Composite do
%i(manual) | false | 'skipped' | false
%i(skipped failed) | false | 'success' | true
%i(skipped failed) | true | 'skipped' | true
+ %i(success manual) | true | 'skipped' | false
+ %i(success manual) | false | 'success' | false
%i(created failed) | false | 'created' | true
%i(preparing manual) | false | 'preparing' | false
end
@@ -80,6 +82,25 @@ RSpec.describe Gitlab::Ci::Status::Composite do
it_behaves_like 'compares status and warnings'
end
+
+ context 'when FF ci_fix_pipeline_status_for_dag_needs_manual is disabled' do
+ before do
+ stub_feature_flags(ci_fix_pipeline_status_for_dag_needs_manual: false)
+ end
+
+ where(:build_statuses, :dag, :result, :has_warnings) do
+ %i(success manual) | true | 'pending' | false
+ %i(success manual) | false | 'success' | false
+ end
+
+ with_them do
+ let(:all_statuses) do
+ build_statuses.map { |status| @statuses_with_allow_failure[status] }
+ end
+
+ it_behaves_like 'compares status and warnings'
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index 641cb0183d3..94a6255f1e2 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -134,4 +134,14 @@ RSpec.describe Gitlab::Ci::Status::Factory do
it_behaves_like 'compound decorator factory'
end
end
+
+ context 'behaviour of FactoryBot traits that create associations' do
+ context 'creating a namespace with an associated aggregation_schedule record' do
+ it 'creates only one Namespace record and one Namespace::AggregationSchedule record' do
+ expect { create(:namespace, :with_aggregation_schedule) }
+ .to change { Namespace.count }.by(1)
+ .and change { Namespace::AggregationSchedule.count }.by(1)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
index f9d6fe24e70..6dfcecb853a 100644
--- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
@@ -3,252 +3,260 @@
require 'spec_helper'
RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
+ using RSpec::Parameterized::TableSyntax
+
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps') }
- describe 'the created pipeline' do
- let(:default_branch) { 'master' }
- let(:pipeline_branch) { default_branch }
- let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
- let(:user) { project.owner }
- let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
- let(:pipeline) { service.execute!(:push) }
- let(:build_names) { pipeline.builds.pluck(:name) }
-
- before do
- stub_ci_pipeline_yaml_file(template.content)
- allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
- allow(project).to receive(:default_branch).and_return(default_branch)
- end
+ where(:default_branch) do
+ %w[master main]
+ end
- shared_examples 'no Kubernetes deployment job' do
- it 'does not create any Kubernetes deployment-related builds' do
- expect(build_names).not_to include('production')
- expect(build_names).not_to include('production_manual')
- expect(build_names).not_to include('staging')
- expect(build_names).not_to include('canary')
- expect(build_names).not_to include('review')
- expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
- end
- end
+ with_them do
+ describe 'the created pipeline' do
+ let(:pipeline_branch) { default_branch }
+ let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.owner }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
+ let(:pipeline) { service.execute!(:push) }
+ let(:build_names) { pipeline.builds.pluck(:name) }
- it 'creates a build and a test job' do
- expect(build_names).to include('build', 'test')
- end
+ before do
+ stub_application_setting(default_branch_name: default_branch)
+ stub_ci_pipeline_yaml_file(template.content)
+ allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
+ end
- context 'when the project is set for deployment to AWS' do
- let(:platform_value) { 'ECS' }
- let(:review_prod_build_names) { build_names.select {|n| n.include?('review') || n.include?('production')} }
+ shared_examples 'no Kubernetes deployment job' do
+ it 'does not create any Kubernetes deployment-related builds' do
+ expect(build_names).not_to include('production')
+ expect(build_names).not_to include('production_manual')
+ expect(build_names).not_to include('staging')
+ expect(build_names).not_to include('canary')
+ expect(build_names).not_to include('review')
+ expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
+ end
+ end
- before do
- create(:ci_variable, project: project, key: 'AUTO_DEVOPS_PLATFORM_TARGET', value: platform_value)
+ it 'creates a build and a test job' do
+ expect(build_names).to include('build', 'test')
end
- shared_examples 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do |job_name|
- context 'when AUTO_DEVOPS_PLATFORM_TARGET is nil' do
- let(:platform_value) { nil }
+ context 'when the project is set for deployment to AWS' do
+ let(:platform_value) { 'ECS' }
+ let(:review_prod_build_names) { build_names.select {|n| n.include?('review') || n.include?('production')} }
- it 'does not trigger the job' do
- expect(build_names).not_to include(job_name)
- end
+ before do
+ create(:ci_variable, project: project, key: 'AUTO_DEVOPS_PLATFORM_TARGET', value: platform_value)
end
- context 'when AUTO_DEVOPS_PLATFORM_TARGET is empty' do
- let(:platform_value) { '' }
+ shared_examples 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do |job_name|
+ context 'when AUTO_DEVOPS_PLATFORM_TARGET is nil' do
+ let(:platform_value) { nil }
- it 'does not trigger the job' do
- expect(build_names).not_to include(job_name)
+ it 'does not trigger the job' do
+ expect(build_names).not_to include(job_name)
+ end
end
- end
- end
- it_behaves_like 'no Kubernetes deployment job'
+ context 'when AUTO_DEVOPS_PLATFORM_TARGET is empty' do
+ let(:platform_value) { '' }
- it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
- let(:job_name) { 'production_ecs' }
- end
+ it 'does not trigger the job' do
+ expect(build_names).not_to include(job_name)
+ end
+ end
+ end
- it 'creates an ECS deployment job for production only' do
- expect(review_prod_build_names).to contain_exactly('production_ecs')
- end
+ it_behaves_like 'no Kubernetes deployment job'
- context 'with FARGATE as a launch type' do
- let(:platform_value) { 'FARGATE' }
+ it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
+ let(:job_name) { 'production_ecs' }
+ end
- it 'creates a FARGATE deployment job for production only' do
- expect(review_prod_build_names).to contain_exactly('production_fargate')
+ it 'creates an ECS deployment job for production only' do
+ expect(review_prod_build_names).to contain_exactly('production_ecs')
end
- end
- context 'and we are not on the default branch' do
- let(:platform_value) { 'ECS' }
- let(:pipeline_branch) { 'patch-1' }
+ context 'with FARGATE as a launch type' do
+ let(:platform_value) { 'FARGATE' }
- before do
- project.repository.create_branch(pipeline_branch)
+ it 'creates a FARGATE deployment job for production only' do
+ expect(review_prod_build_names).to contain_exactly('production_fargate')
+ end
end
- %w(review_ecs review_fargate).each do |job|
- it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
- let(:job_name) { job }
+ context 'and we are not on the default branch' do
+ let(:platform_value) { 'ECS' }
+ let(:pipeline_branch) { 'patch-1' }
+
+ before do
+ project.repository.create_branch(pipeline_branch, default_branch)
end
- end
- it 'creates an ECS deployment job for review only' do
- expect(review_prod_build_names).to contain_exactly('review_ecs', 'stop_review_ecs')
- end
+ %w(review_ecs review_fargate).each do |job|
+ it_behaves_like 'no ECS job when AUTO_DEVOPS_PLATFORM_TARGET is not present' do
+ let(:job_name) { job }
+ end
+ end
- context 'with FARGATE as a launch type' do
- let(:platform_value) { 'FARGATE' }
+ it 'creates an ECS deployment job for review only' do
+ expect(review_prod_build_names).to contain_exactly('review_ecs', 'stop_review_ecs')
+ end
+
+ context 'with FARGATE as a launch type' do
+ let(:platform_value) { 'FARGATE' }
- it 'creates an FARGATE deployment job for review only' do
- expect(review_prod_build_names).to contain_exactly('review_fargate', 'stop_review_fargate')
+ it 'creates an FARGATE deployment job for review only' do
+ expect(review_prod_build_names).to contain_exactly('review_fargate', 'stop_review_fargate')
+ end
end
end
- end
- context 'and when the project has an active cluster' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) }
+ context 'and when the project has an active cluster' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) }
- before do
- allow(cluster).to receive(:active?).and_return(true)
- end
+ before do
+ allow(cluster).to receive(:active?).and_return(true)
+ end
- context 'on default branch' do
- it 'triggers the deployment to Kubernetes, not to ECS' do
- expect(build_names).not_to include('review')
- expect(build_names).to include('production')
- expect(build_names).not_to include('production_ecs')
- expect(build_names).not_to include('review_ecs')
+ context 'on default branch' do
+ it 'triggers the deployment to Kubernetes, not to ECS' do
+ expect(build_names).not_to include('review')
+ expect(build_names).to include('production')
+ expect(build_names).not_to include('production_ecs')
+ expect(build_names).not_to include('review_ecs')
+ end
end
end
- end
- context 'when the platform target is EC2' do
- let(:platform_value) { 'EC2' }
+ context 'when the platform target is EC2' do
+ let(:platform_value) { 'EC2' }
- it 'contains the build_artifact job, not the build job' do
- expect(build_names).to include('build_artifact')
- expect(build_names).not_to include('build')
+ it 'contains the build_artifact job, not the build job' do
+ expect(build_names).to include('build_artifact')
+ expect(build_names).not_to include('build')
+ end
end
end
- end
-
- context 'when the project has no active cluster' do
- it 'only creates a build and a test stage' do
- expect(pipeline.stages_names).to eq(%w(build test))
- end
- it_behaves_like 'no Kubernetes deployment job'
- end
+ context 'when the project has no active cluster' do
+ it 'only creates a build and a test stage' do
+ expect(pipeline.stages_names).to eq(%w(build test))
+ end
- context 'when the project has an active cluster' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) }
-
- describe 'deployment-related builds' do
- context 'on default branch' do
- it 'does not include rollout jobs besides production' do
- expect(build_names).to include('production')
- expect(build_names).not_to include('production_manual')
- expect(build_names).not_to include('staging')
- expect(build_names).not_to include('canary')
- expect(build_names).not_to include('review')
- expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
- end
+ it_behaves_like 'no Kubernetes deployment job'
+ end
- context 'when STAGING_ENABLED=1' do
- before do
- create(:ci_variable, project: project, key: 'STAGING_ENABLED', value: '1')
- end
+ context 'when the project has an active cluster' do
+ let!(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) }
- it 'includes a staging job and a production_manual job' do
- expect(build_names).not_to include('production')
- expect(build_names).to include('production_manual')
- expect(build_names).to include('staging')
+ describe 'deployment-related builds' do
+ context 'on default branch' do
+ it 'does not include rollout jobs besides production' do
+ expect(build_names).to include('production')
+ expect(build_names).not_to include('production_manual')
+ expect(build_names).not_to include('staging')
expect(build_names).not_to include('canary')
expect(build_names).not_to include('review')
expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
end
+
+ context 'when STAGING_ENABLED=1' do
+ before do
+ create(:ci_variable, project: project, key: 'STAGING_ENABLED', value: '1')
+ end
+
+ it 'includes a staging job and a production_manual job' do
+ expect(build_names).not_to include('production')
+ expect(build_names).to include('production_manual')
+ expect(build_names).to include('staging')
+ expect(build_names).not_to include('canary')
+ expect(build_names).not_to include('review')
+ expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
+ end
+ end
+
+ context 'when CANARY_ENABLED=1' do
+ before do
+ create(:ci_variable, project: project, key: 'CANARY_ENABLED', value: '1')
+ end
+
+ it 'includes a canary job and a production_manual job' do
+ expect(build_names).not_to include('production')
+ expect(build_names).to include('production_manual')
+ expect(build_names).not_to include('staging')
+ expect(build_names).to include('canary')
+ expect(build_names).not_to include('review')
+ expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
+ end
+ end
end
- context 'when CANARY_ENABLED=1' do
+ context 'outside of default branch' do
+ let(:pipeline_branch) { 'patch-1' }
+
before do
- create(:ci_variable, project: project, key: 'CANARY_ENABLED', value: '1')
+ project.repository.create_branch(pipeline_branch, default_branch)
end
- it 'includes a canary job and a production_manual job' do
+ it 'does not include rollout jobs besides review' do
expect(build_names).not_to include('production')
- expect(build_names).to include('production_manual')
+ expect(build_names).not_to include('production_manual')
expect(build_names).not_to include('staging')
- expect(build_names).to include('canary')
- expect(build_names).not_to include('review')
+ expect(build_names).not_to include('canary')
+ expect(build_names).to include('review')
expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
end
end
end
-
- context 'outside of default branch' do
- let(:pipeline_branch) { 'patch-1' }
-
- before do
- project.repository.create_branch(pipeline_branch)
- end
-
- it 'does not include rollout jobs besides review' do
- expect(build_names).not_to include('production')
- expect(build_names).not_to include('production_manual')
- expect(build_names).not_to include('staging')
- expect(build_names).not_to include('canary')
- expect(build_names).to include('review')
- expect(build_names).not_to include(a_string_matching(/rollout \d+%/))
- end
- end
end
end
- end
- describe 'build-pack detection' do
- using RSpec::Parameterized::TableSyntax
-
- where(:case_name, :files, :variables, :include_build_names, :not_include_build_names) do
- 'No match' | { 'README.md' => '' } | {} | %w() | %w(build test)
- 'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w(build test) | %w()
- 'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w(build test) | %w()
- 'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w() | %w(build test)
- 'DOCKERFILE_PATH' | { 'README.md' => '' } | { 'DOCKERFILE_PATH' => 'Docker.file' } | %w(build test) | %w()
- 'Dockerfile' | { 'Dockerfile' => '' } | {} | %w(build test) | %w()
- 'Clojure' | { 'project.clj' => '' } | {} | %w(build test) | %w()
- 'Go modules' | { 'go.mod' => '' } | {} | %w(build test) | %w()
- 'Go gb' | { 'src/gitlab.com/gopackage.go' => '' } | {} | %w(build test) | %w()
- 'Gradle' | { 'gradlew' => '' } | {} | %w(build test) | %w()
- 'Java' | { 'pom.xml' => '' } | {} | %w(build test) | %w()
- 'Multi-buildpack' | { '.buildpacks' => '' } | {} | %w(build test) | %w()
- 'NodeJS' | { 'package.json' => '' } | {} | %w(build test) | %w()
- 'PHP' | { 'composer.json' => '' } | {} | %w(build test) | %w()
- 'Play' | { 'conf/application.conf' => '' } | {} | %w(build test) | %w()
- 'Python' | { 'Pipfile' => '' } | {} | %w(build test) | %w()
- 'Ruby' | { 'Gemfile' => '' } | {} | %w(build test) | %w()
- 'Scala' | { 'build.sbt' => '' } | {} | %w(build test) | %w()
- 'Static' | { '.static' => '' } | {} | %w(build test) | %w()
- end
+ describe 'build-pack detection' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:case_name, :files, :variables, :include_build_names, :not_include_build_names) do
+ 'No match' | { 'README.md' => '' } | {} | %w() | %w(build test)
+ 'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w(build test) | %w()
+ 'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w(build test) | %w()
+ 'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w() | %w(build test)
+ 'DOCKERFILE_PATH' | { 'README.md' => '' } | { 'DOCKERFILE_PATH' => 'Docker.file' } | %w(build test) | %w()
+ 'Dockerfile' | { 'Dockerfile' => '' } | {} | %w(build test) | %w()
+ 'Clojure' | { 'project.clj' => '' } | {} | %w(build test) | %w()
+ 'Go modules' | { 'go.mod' => '' } | {} | %w(build test) | %w()
+ 'Go gb' | { 'src/gitlab.com/gopackage.go' => '' } | {} | %w(build test) | %w()
+ 'Gradle' | { 'gradlew' => '' } | {} | %w(build test) | %w()
+ 'Java' | { 'pom.xml' => '' } | {} | %w(build test) | %w()
+ 'Multi-buildpack' | { '.buildpacks' => '' } | {} | %w(build test) | %w()
+ 'NodeJS' | { 'package.json' => '' } | {} | %w(build test) | %w()
+ 'PHP' | { 'composer.json' => '' } | {} | %w(build test) | %w()
+ 'Play' | { 'conf/application.conf' => '' } | {} | %w(build test) | %w()
+ 'Python' | { 'Pipfile' => '' } | {} | %w(build test) | %w()
+ 'Ruby' | { 'Gemfile' => '' } | {} | %w(build test) | %w()
+ 'Scala' | { 'build.sbt' => '' } | {} | %w(build test) | %w()
+ 'Static' | { '.static' => '' } | {} | %w(build test) | %w()
+ end
- with_them do
- let(:project) { create(:project, :custom_repo, files: files) }
- let(:user) { project.owner }
- let(:service) { Ci::CreatePipelineService.new(project, user, ref: 'master' ) }
- let(:pipeline) { service.execute(:push) }
- let(:build_names) { pipeline.builds.pluck(:name) }
+ with_them do
+ let(:project) { create(:project, :custom_repo, files: files) }
+ let(:user) { project.owner }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: default_branch ) }
+ let(:pipeline) { service.execute(:push) }
+ let(:build_names) { pipeline.builds.pluck(:name) }
- before do
- stub_ci_pipeline_yaml_file(template.content)
- allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
- variables.each do |(key, value)|
- create(:ci_variable, project: project, key: key, value: value)
+ before do
+ stub_application_setting(default_branch_name: default_branch)
+ stub_ci_pipeline_yaml_file(template.content)
+ allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
+ variables.each do |(key, value)|
+ create(:ci_variable, project: project, key: key, value: value)
+ end
end
- end
- it 'creates a pipeline with the expected jobs' do
- expect(build_names).to include(*include_build_names)
- expect(build_names).not_to include(*not_include_build_names)
+ it 'creates a pipeline with the expected jobs' do
+ expect(build_names).to include(*include_build_names)
+ expect(build_names).not_to include(*not_include_build_names)
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 92bf2519588..597e4ca9b03 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_default: :keep do
- let_it_be(:project) { create_default(:project) }
+ let_it_be(:project) { create_default(:project).freeze }
let_it_be_with_reload(:build) { create(:ci_build) }
let(:trace) { described_class.new(build) }
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index 2e43f22830a..ca9dc95711d 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -32,6 +32,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do
it 'saves given value' do
expect(subject[:key]).to eq variable_key
expect(subject[:value]).to eq expected_value
+ expect(subject.value).to eq expected_value
end
end
@@ -69,6 +70,47 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do
end
end
+ describe '#depends_on' do
+ let(:item) { Gitlab::Ci::Variables::Collection::Item.new(**variable) }
+
+ subject { item.depends_on }
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "no variable references": {
+ variable: { key: 'VAR', value: 'something' },
+ expected_depends_on: nil
+ },
+ "simple variable reference": {
+ variable: { key: 'VAR', value: 'something_$VAR2' },
+ expected_depends_on: %w(VAR2)
+ },
+ "complex expansion": {
+ variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3' },
+ expected_depends_on: %w(VAR2 VAR3)
+ },
+ "complex expansion in raw variable": {
+ variable: { key: 'VAR', value: 'something_${VAR2}_$VAR3', raw: true },
+ expected_depends_on: nil
+ },
+ "complex expansions for Windows": {
+ variable: { key: 'variable3', value: 'key%variable%%variable2%' },
+ expected_depends_on: %w(variable variable2)
+ }
+ }
+ end
+
+ with_them do
+ it 'contains referenced variable names' do
+ is_expected.to eq(expected_depends_on)
+ end
+ end
+ end
+ end
+
describe '.fabricate' do
it 'supports using a hash' do
resource = described_class.fabricate(variable)
@@ -118,6 +160,26 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do
end
end
+ describe '#raw' do
+ it 'returns false when :raw is not specified' do
+ item = described_class.new(**variable)
+
+ expect(item.raw).to eq false
+ end
+
+ context 'when :raw is specified as true' do
+ let(:variable) do
+ { key: variable_key, value: variable_value, public: true, masked: false, raw: true }
+ end
+
+ it 'returns true' do
+ item = described_class.new(**variable)
+
+ expect(item.raw).to eq true
+ end
+ end
+ end
+
describe '#to_runner_variable' do
context 'when variable is not a file-related' do
it 'returns a runner-compatible hash representation' do
@@ -139,5 +201,47 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do
.to eq(key: 'VAR', value: 'value', public: true, file: true, masked: false)
end
end
+
+ context 'when variable is raw' do
+ it 'does not export raw value when it is false' do
+ runner_variable = described_class
+ .new(key: 'VAR', value: 'value', raw: false)
+ .to_runner_variable
+
+ expect(runner_variable)
+ .to eq(key: 'VAR', value: 'value', public: true, masked: false)
+ end
+
+ it 'exports raw value when it is true' do
+ runner_variable = described_class
+ .new(key: 'VAR', value: 'value', raw: true)
+ .to_runner_variable
+
+ expect(runner_variable)
+ .to eq(key: 'VAR', value: 'value', public: true, raw: true, masked: false)
+ end
+ end
+
+ context 'when referencing a variable' do
+ it '#depends_on contains names of dependencies' do
+ runner_variable = described_class.new(key: 'CI_VAR', value: '${CI_VAR_2}-123-$CI_VAR_3')
+
+ expect(runner_variable.depends_on).to eq(%w(CI_VAR_2 CI_VAR_3))
+ end
+ end
+
+ context 'when assigned the raw attribute' do
+ it 'retains a true raw attribute' do
+ runner_variable = described_class.new(key: 'CI_VAR', value: '123', raw: true)
+
+ expect(runner_variable).to eq(key: 'CI_VAR', value: '123', public: true, masked: false, raw: true)
+ end
+
+ it 'does not retain a false raw attribute' do
+ runner_variable = described_class.new(key: 'CI_VAR', value: '123', raw: false)
+
+ expect(runner_variable).to eq(key: 'CI_VAR', value: '123', public: true, masked: false)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/variables/collection/sort_spec.rb b/spec/lib/gitlab/ci/variables/collection/sort_spec.rb
new file mode 100644
index 00000000000..73cf0e19d00
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection/sort_spec.rb
@@ -0,0 +1,185 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Variables::Collection::Sort do
+ describe '#initialize with non-Collection value' do
+ context 'when FF :variable_inside_variable is disabled' do
+ subject { Gitlab::Ci::Variables::Collection::Sort.new([]) }
+
+ it 'raises ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError, /Collection object was expected/)
+ end
+ end
+
+ context 'when FF :variable_inside_variable is enabled' do
+ subject { Gitlab::Ci::Variables::Collection::Sort.new([]) }
+
+ it 'raises ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError, /Collection object was expected/)
+ end
+ end
+ end
+
+ describe '#errors' do
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: [],
+ expected_errors: nil
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ expected_errors: nil
+ },
+ "cyclic dependency": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ expected_errors: 'circular variable reference detected: ["variable", "variable2", "variable3"]'
+ },
+ "array with raw variable": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2', raw: true }
+ ],
+ expected_errors: nil
+ },
+ "variable containing escaped variable reference": {
+ variables: [
+ { key: 'variable_a', value: 'value' },
+ { key: 'variable_b', value: '$$variable_a' },
+ { key: 'variable_c', value: '$variable_b' }
+ ],
+ expected_errors: nil
+ }
+ }
+ end
+
+ with_them do
+ let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
+
+ subject { Gitlab::Ci::Variables::Collection::Sort.new(collection) }
+
+ it 'errors matches expected errors' do
+ expect(subject.errors).to eq(expected_errors)
+ end
+
+ it 'valid? matches expected errors' do
+ expect(subject.valid?).to eq(expected_errors.nil?)
+ end
+
+ it 'does not raise' do
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ describe '#tsort' do
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: [],
+ result: []
+ },
+ "simple expansions, no reordering needed": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ result: %w[variable variable2 variable3]
+ },
+ "complex expansion, reordering needed": {
+ variables: [
+ { key: 'variable2', value: 'key${variable}' },
+ { key: 'variable', value: 'value' }
+ ],
+ result: %w[variable variable2]
+ },
+ "unused variables": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable4', value: 'key$variable$variable3' },
+ { key: 'variable2', value: 'result2' },
+ { key: 'variable3', value: 'result3' }
+ ],
+ result: %w[variable variable3 variable4 variable2]
+ },
+ "missing variable": {
+ variables: [
+ { key: 'variable2', value: 'key$variable' }
+ ],
+ result: %w[variable2]
+ },
+ "complex expansions with missing variable": {
+ variables: [
+ { key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'value3' }
+ ],
+ result: %w[variable variable3 variable4]
+ },
+ "raw variable does not get resolved": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2', raw: true }
+ ],
+ result: %w[variable3 variable2 variable]
+ },
+ "variable containing escaped variable reference": {
+ variables: [
+ { key: 'variable_c', value: '$variable_b' },
+ { key: 'variable_b', value: '$$variable_a' },
+ { key: 'variable_a', value: 'value' }
+ ],
+ result: %w[variable_a variable_b variable_c]
+ }
+ }
+ end
+
+ with_them do
+ let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
+
+ subject { Gitlab::Ci::Variables::Collection::Sort.new(collection).tsort }
+
+ it 'returns correctly sorted variables' do
+ expect(subject.pluck(:key)).to eq(result)
+ end
+ end
+ end
+
+ context 'cyclic dependency' do
+ let(:variables) do
+ [
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' },
+ { key: 'variable', value: '$variable2' }
+ ]
+ end
+
+ let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
+
+ subject { Gitlab::Ci::Variables::Collection::Sort.new(collection).tsort }
+
+ it 'raises TSort::Cyclic' do
+ expect { subject }.to raise_error(TSort::Cyclic)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb b/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb
deleted file mode 100644
index 954273fd41e..00000000000
--- a/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb
+++ /dev/null
@@ -1,259 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Ci::Variables::Collection::Sorted do
- describe '#errors' do
- context 'when FF :variable_inside_variable is disabled' do
- let_it_be(:project_with_flag_disabled) { create(:project) }
- let_it_be(:project_with_flag_enabled) { create(:project) }
-
- before do
- stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
- end
-
- context 'table tests' do
- using RSpec::Parameterized::TableSyntax
-
- where do
- {
- "empty array": {
- variables: []
- },
- "simple expansions": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ]
- },
- "complex expansion": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'key${variable}' }
- ]
- },
- "complex expansions with missing variable for Windows": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable3', value: 'key%variable%%variable2%' }
- ]
- },
- "out-of-order variable reference": {
- variables: [
- { key: 'variable2', value: 'key${variable}' },
- { key: 'variable', value: 'value' }
- ]
- },
- "array with cyclic dependency": {
- variables: [
- { key: 'variable', value: '$variable2' },
- { key: 'variable2', value: '$variable3' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ]
- }
- }
- end
-
- with_them do
- subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables, project_with_flag_disabled) }
-
- it 'does not report error' do
- expect(subject.errors).to eq(nil)
- end
-
- it 'valid? reports true' do
- expect(subject.valid?).to eq(true)
- end
- end
- end
- end
-
- context 'when FF :variable_inside_variable is enabled' do
- let_it_be(:project_with_flag_disabled) { create(:project) }
- let_it_be(:project_with_flag_enabled) { create(:project) }
-
- before do
- stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
- end
-
- context 'table tests' do
- using RSpec::Parameterized::TableSyntax
-
- where do
- {
- "empty array": {
- variables: [],
- validation_result: nil
- },
- "simple expansions": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ],
- validation_result: nil
- },
- "cyclic dependency": {
- variables: [
- { key: 'variable', value: '$variable2' },
- { key: 'variable2', value: '$variable3' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ],
- validation_result: 'circular variable reference detected: ["variable", "variable2", "variable3"]'
- }
- }
- end
-
- with_them do
- subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables, project_with_flag_enabled) }
-
- it 'errors matches expected validation result' do
- expect(subject.errors).to eq(validation_result)
- end
-
- it 'valid? matches expected validation result' do
- expect(subject.valid?).to eq(validation_result.nil?)
- end
- end
- end
- end
- end
-
- describe '#sort' do
- context 'when FF :variable_inside_variable is disabled' do
- before do
- stub_feature_flags(variable_inside_variable: false)
- end
-
- context 'table tests' do
- using RSpec::Parameterized::TableSyntax
-
- where do
- {
- "empty array": {
- variables: []
- },
- "simple expansions": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ]
- },
- "complex expansion": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'key${variable}' }
- ]
- },
- "complex expansions with missing variable for Windows": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable3', value: 'key%variable%%variable2%' }
- ]
- },
- "out-of-order variable reference": {
- variables: [
- { key: 'variable2', value: 'key${variable}' },
- { key: 'variable', value: 'value' }
- ]
- },
- "array with cyclic dependency": {
- variables: [
- { key: 'variable', value: '$variable2' },
- { key: 'variable2', value: '$variable3' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ]
- }
- }
- end
-
- with_them do
- let_it_be(:project) { create(:project) }
- subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables, project) }
-
- it 'does not expand variables' do
- expect(subject.sort).to eq(variables)
- end
- end
- end
- end
-
- context 'when FF :variable_inside_variable is enabled' do
- before do
- stub_licensed_features(group_saml_group_sync: true)
- stub_feature_flags(saml_group_links: true)
- stub_feature_flags(variable_inside_variable: true)
- end
-
- context 'table tests' do
- using RSpec::Parameterized::TableSyntax
-
- where do
- {
- "empty array": {
- variables: [],
- result: []
- },
- "simple expansions, no reordering needed": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ],
- result: %w[variable variable2 variable3]
- },
- "complex expansion, reordering needed": {
- variables: [
- { key: 'variable2', value: 'key${variable}' },
- { key: 'variable', value: 'value' }
- ],
- result: %w[variable variable2]
- },
- "unused variables": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable4', value: 'key$variable$variable3' },
- { key: 'variable2', value: 'result2' },
- { key: 'variable3', value: 'result3' }
- ],
- result: %w[variable variable3 variable4 variable2]
- },
- "missing variable": {
- variables: [
- { key: 'variable2', value: 'key$variable' }
- ],
- result: %w[variable2]
- },
- "complex expansions with missing variable": {
- variables: [
- { key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
- { key: 'variable', value: 'value' },
- { key: 'variable3', value: 'value3' }
- ],
- result: %w[variable variable3 variable4]
- },
- "cyclic dependency causes original array to be returned": {
- variables: [
- { key: 'variable2', value: '$variable3' },
- { key: 'variable3', value: 'key$variable$variable2' },
- { key: 'variable', value: '$variable2' }
- ],
- result: %w[variable2 variable3 variable]
- }
- }
- end
-
- with_them do
- let_it_be(:project) { create(:project) }
- subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables, project) }
-
- it 'sort returns correctly sorted variables' do
- expect(subject.sort.map { |var| var[:key] }).to eq(result)
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index ac84313ad9f..7b77754190a 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
end
it 'can be initialized without an argument' do
- expect(subject).to be_none
+ is_expected.to be_none
end
end
@@ -21,13 +21,13 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
it 'appends a hash' do
subject.append(key: 'VARIABLE', value: 'something')
- expect(subject).to be_one
+ is_expected.to be_one
end
it 'appends a Ci::Variable' do
subject.append(build(:ci_variable))
- expect(subject).to be_one
+ is_expected.to be_one
end
it 'appends an internal resource' do
@@ -35,7 +35,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
subject.append(collection.first)
- expect(subject).to be_one
+ is_expected.to be_one
end
it 'returns self' do
@@ -98,6 +98,50 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
end
end
+ describe '#[]' do
+ variable = { key: 'VAR', value: 'value', public: true, masked: false }
+
+ collection = described_class.new([variable])
+
+ it 'returns nil for a non-existent variable name' do
+ expect(collection['UNKNOWN_VAR']).to be_nil
+ end
+
+ it 'returns Item for an existent variable name' do
+ expect(collection['VAR']).to be_an_instance_of(Gitlab::Ci::Variables::Collection::Item)
+ expect(collection['VAR'].to_runner_variable).to eq(variable)
+ end
+ end
+
+ describe '#size' do
+ it 'returns zero for empty collection' do
+ collection = described_class.new([])
+
+ expect(collection.size).to eq(0)
+ end
+
+ it 'returns 2 for collection with 2 variables' do
+ collection = described_class.new(
+ [
+ { key: 'VAR1', value: 'value', public: true, masked: false },
+ { key: 'VAR2', value: 'value', public: true, masked: false }
+ ])
+
+ expect(collection.size).to eq(2)
+ end
+
+ it 'returns 3 for collection with 2 duplicate variables' do
+ collection = described_class.new(
+ [
+ { key: 'VAR1', value: 'value', public: true, masked: false },
+ { key: 'VAR2', value: 'value', public: true, masked: false },
+ { key: 'VAR1', value: 'value', public: true, masked: false }
+ ])
+
+ expect(collection.size).to eq(3)
+ end
+ end
+
describe '#to_runner_variables' do
it 'creates an array of hashes in a runner-compatible format' do
collection = described_class.new([{ key: 'TEST', value: '1' }])
@@ -121,4 +165,338 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
expect(collection.to_hash).not_to include(TEST1: 'test-1')
end
end
+
+ describe '#reject' do
+ let(:collection) do
+ described_class.new
+ .append(key: 'CI_JOB_NAME', value: 'test-1')
+ .append(key: 'CI_BUILD_ID', value: '1')
+ .append(key: 'TEST1', value: 'test-3')
+ end
+
+ subject { collection.reject { |var| var[:key] =~ /\ACI_(JOB|BUILD)/ } }
+
+ it 'returns a Collection instance' do
+ is_expected.to be_an_instance_of(described_class)
+ end
+
+ it 'returns correctly filtered Collection' do
+ comp = collection.to_runner_variables.reject { |var| var[:key] =~ /\ACI_(JOB|BUILD)/ }
+ expect(subject.to_runner_variables).to eq(comp)
+ end
+ end
+
+ describe '#expand_value' do
+ let(:collection) do
+ Gitlab::Ci::Variables::Collection.new
+ .append(key: 'CI_JOB_NAME', value: 'test-1')
+ .append(key: 'CI_BUILD_ID', value: '1')
+ .append(key: 'RAW_VAR', value: '$TEST1', raw: true)
+ .append(key: 'TEST1', value: 'test-3')
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty value": {
+ value: '',
+ result: '',
+ keep_undefined: false
+ },
+ "simple expansions": {
+ value: 'key$TEST1-$CI_BUILD_ID',
+ result: 'keytest-3-1',
+ keep_undefined: false
+ },
+ "complex expansion": {
+ value: 'key${TEST1}-${CI_JOB_NAME}',
+ result: 'keytest-3-test-1',
+ keep_undefined: false
+ },
+ "complex expansions with raw variable": {
+ value: 'key${RAW_VAR}-${CI_JOB_NAME}',
+ result: 'key$TEST1-test-1',
+ keep_undefined: false
+ },
+ "missing variable not keeping original": {
+ value: 'key${MISSING_VAR}-${CI_JOB_NAME}',
+ result: 'key-test-1',
+ keep_undefined: false
+ },
+ "missing variable keeping original": {
+ value: 'key${MISSING_VAR}-${CI_JOB_NAME}',
+ result: 'key${MISSING_VAR}-test-1',
+ keep_undefined: true
+ }
+ }
+ end
+
+ with_them do
+ subject { collection.expand_value(value, keep_undefined: keep_undefined) }
+
+ it 'matches expected expansion' do
+ is_expected.to eq(result)
+ end
+ end
+ end
+ end
+
+ describe '#sort_and_expand_all' do
+ context 'when FF :variable_inside_variable is disabled' do
+ let_it_be(:project_with_flag_disabled) { create(:project) }
+ let_it_be(:project_with_flag_enabled) { create(:project) }
+
+ before do
+ stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: [],
+ keep_undefined: false
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ keep_undefined: false
+ },
+ "complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'key${variable}' }
+ ],
+ keep_undefined: false
+ },
+ "out-of-order variable reference": {
+ variables: [
+ { key: 'variable2', value: 'key${variable}' },
+ { key: 'variable', value: 'value' }
+ ],
+ keep_undefined: false
+ },
+ "complex expansions with raw variable": {
+ variables: [
+ { key: 'variable3', value: 'key_${variable}_${variable2}' },
+ { key: 'variable', value: '$variable2', raw: true },
+ { key: 'variable2', value: 'value2' }
+ ],
+ keep_undefined: false
+ },
+ "array with cyclic dependency": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ keep_undefined: true
+ }
+ }
+ end
+
+ with_them do
+ let(:collection) { Gitlab::Ci::Variables::Collection.new(variables, keep_undefined: keep_undefined) }
+
+ subject { collection.sort_and_expand_all(project_with_flag_disabled) }
+
+ it 'returns Collection' do
+ is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
+ end
+
+ it 'does not expand variables' do
+ var_hash = variables.pluck(:key, :value).to_h
+ expect(subject.to_hash).to eq(var_hash)
+ end
+ end
+ end
+ end
+
+ context 'when FF :variable_inside_variable is enabled' do
+ let_it_be(:project_with_flag_disabled) { create(:project) }
+ let_it_be(:project_with_flag_enabled) { create(:project) }
+
+ before do
+ stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: [],
+ keep_undefined: false,
+ result: []
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' },
+ { key: 'variable4', value: 'key$variable$variable3' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'keyvalueresult' },
+ { key: 'variable4', value: 'keyvaluekeyvalueresult' }
+ ]
+ },
+ "complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'key${variable}' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'keyvalue' }
+ ]
+ },
+ "unused variables": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result2' },
+ { key: 'variable3', value: 'result3' },
+ { key: 'variable4', value: 'key$variable$variable3' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result2' },
+ { key: 'variable3', value: 'result3' },
+ { key: 'variable4', value: 'keyvalueresult3' }
+ ]
+ },
+ "complex expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key${variable}${variable2}' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'keyvalueresult' }
+ ]
+ },
+ "out-of-order expansion": {
+ variables: [
+ { key: 'variable3', value: 'key$variable2$variable' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable2', value: 'result' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'keyresultvalue' }
+ ]
+ },
+ "out-of-order complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key${variable2}${variable}' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'keyresultvalue' }
+ ]
+ },
+ "missing variable": {
+ variables: [
+ { key: 'variable2', value: 'key$variable' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable2', value: 'key' }
+ ]
+ },
+ "missing variable keeping original": {
+ variables: [
+ { key: 'variable2', value: 'key$variable' }
+ ],
+ keep_undefined: true,
+ result: [
+ { key: 'variable2', value: 'key$variable' }
+ ]
+ },
+ "complex expansions with missing variable keeping original": {
+ variables: [
+ { key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'value3' }
+ ],
+ keep_undefined: true,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'value3' },
+ { key: 'variable4', value: 'keyvalue${variable2}value3' }
+ ]
+ },
+ "complex expansions with raw variable": {
+ variables: [
+ { key: 'variable3', value: 'key_${variable}_${variable2}' },
+ { key: 'variable', value: '$variable2', raw: true },
+ { key: 'variable2', value: 'value2' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: '$variable2', raw: true },
+ { key: 'variable2', value: 'value2' },
+ { key: 'variable3', value: 'key_$variable2_value2' }
+ ]
+ },
+ "cyclic dependency causes original array to be returned": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ }
+ }
+ end
+
+ with_them do
+ let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
+
+ subject { collection.sort_and_expand_all(project_with_flag_enabled, keep_undefined: keep_undefined) }
+
+ it 'returns Collection' do
+ is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
+ end
+
+ it 'expands variables' do
+ var_hash = result.to_h { |env| [env.fetch(:key), env.fetch(:value)] }
+ .with_indifferent_access
+ expect(subject.to_hash).to eq(var_hash)
+ end
+
+ it 'preserves raw attribute' do
+ expect(subject.pluck(:key, :raw).to_h).to eq(collection.pluck(:key, :raw).to_h)
+ end
+ 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 9498453852a..5462a587d16 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1368,6 +1368,155 @@ module Gitlab
end
end
+ context 'with multiple_cache_per_job FF disabled' do
+ before do
+ stub_feature_flags(multiple_cache_per_job: false)
+ end
+ describe 'cache' do
+ context 'when cache definition has unknown keys' do
+ let(:config) do
+ YAML.dump(
+ { cache: { untracked: true, invalid: 'key' },
+ rspec: { script: 'rspec' } })
+ end
+
+ it_behaves_like 'returns errors', 'cache config contains unknown keys: invalid'
+ end
+
+ it "returns cache when defined globally" do
+ config = YAML.dump({
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
+ rspec: {
+ script: "rspec"
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
+
+ expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+ expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
+ paths: ["logs/", "binaries/"],
+ untracked: true,
+ key: 'key',
+ policy: 'pull-push',
+ when: 'on_success'
+ )
+ end
+
+ it "returns cache when defined in default context" do
+ config = YAML.dump(
+ {
+ default: {
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: { files: ['file'] } }
+ },
+ rspec: {
+ script: "rspec"
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
+
+ expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+ expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
+ paths: ["logs/", "binaries/"],
+ untracked: true,
+ key: { files: ['file'] },
+ policy: 'pull-push',
+ when: 'on_success'
+ )
+ end
+
+ it 'returns cache key when defined in a job' do
+ config = YAML.dump({
+ rspec: {
+ cache: { paths: ['logs/', 'binaries/'], untracked: true, key: 'key' },
+ script: 'rspec'
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
+
+ expect(config_processor.stage_builds_attributes('test').size).to eq(1)
+ expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
+ paths: ['logs/', 'binaries/'],
+ untracked: true,
+ key: 'key',
+ policy: 'pull-push',
+ when: 'on_success'
+ )
+ end
+
+ it 'returns cache files' do
+ config = YAML.dump(
+ rspec: {
+ cache: {
+ paths: ['logs/', 'binaries/'],
+ untracked: true,
+ key: { files: ['file'] }
+ },
+ script: 'rspec'
+ }
+ )
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
+
+ expect(config_processor.stage_builds_attributes('test').size).to eq(1)
+ expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
+ paths: ['logs/', 'binaries/'],
+ untracked: true,
+ key: { files: ['file'] },
+ policy: 'pull-push',
+ when: 'on_success'
+ )
+ end
+
+ it 'returns cache files with prefix' do
+ config = YAML.dump(
+ rspec: {
+ cache: {
+ paths: ['logs/', 'binaries/'],
+ untracked: true,
+ key: { files: ['file'], prefix: 'prefix' }
+ },
+ script: 'rspec'
+ }
+ )
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
+
+ expect(config_processor.stage_builds_attributes('test').size).to eq(1)
+ expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
+ paths: ['logs/', 'binaries/'],
+ untracked: true,
+ key: { files: ['file'], prefix: 'prefix' },
+ policy: 'pull-push',
+ when: 'on_success'
+ )
+ end
+
+ it "overwrite cache when defined for a job and globally" do
+ config = YAML.dump({
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
+ rspec: {
+ script: "rspec",
+ cache: { paths: ["test/"], untracked: false, key: 'local' }
+ }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
+
+ expect(config_processor.stage_builds_attributes("test").size).to eq(1)
+ expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
+ paths: ["test/"],
+ untracked: false,
+ key: 'local',
+ policy: 'pull-push',
+ when: 'on_success'
+ )
+ end
+ end
+ end
+
describe 'cache' do
context 'when cache definition has unknown keys' do
let(:config) do
@@ -1381,22 +1530,22 @@ module Gitlab
it "returns cache when defined globally" do
config = YAML.dump({
- cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
- rspec: {
- script: "rspec"
- }
- })
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
+ rspec: {
+ script: "rspec"
+ }
+ })
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
- expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
+ expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq([
paths: ["logs/", "binaries/"],
untracked: true,
key: 'key',
policy: 'pull-push',
when: 'on_success'
- )
+ ])
end
it "returns cache when defined in default context" do
@@ -1413,32 +1562,46 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
- expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
+ expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq([
paths: ["logs/", "binaries/"],
untracked: true,
key: { files: ['file'] },
policy: 'pull-push',
when: 'on_success'
- )
+ ])
end
- it 'returns cache key when defined in a job' do
+ it 'returns cache key/s when defined in a job' do
config = YAML.dump({
- rspec: {
- cache: { paths: ['logs/', 'binaries/'], untracked: true, key: 'key' },
- script: 'rspec'
- }
- })
+ rspec: {
+ cache: [
+ { paths: ['binaries/'], untracked: true, key: 'keya' },
+ { paths: ['logs/', 'binaries/'], untracked: true, key: 'key' }
+ ],
+ script: 'rspec'
+ }
+ })
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
- paths: ['logs/', 'binaries/'],
- untracked: true,
- key: 'key',
- policy: 'pull-push',
- when: 'on_success'
+ [
+ {
+ paths: ['binaries/'],
+ untracked: true,
+ key: 'keya',
+ policy: 'pull-push',
+ when: 'on_success'
+ },
+ {
+ paths: ['logs/', 'binaries/'],
+ untracked: true,
+ key: 'key',
+ policy: 'pull-push',
+ when: 'on_success'
+ }
+ ]
)
end
@@ -1446,10 +1609,10 @@ module Gitlab
config = YAML.dump(
rspec: {
cache: {
- paths: ['logs/', 'binaries/'],
- untracked: true,
- key: { files: ['file'] }
- },
+ paths: ['binaries/'],
+ untracked: true,
+ key: { files: ['file'] }
+ },
script: 'rspec'
}
)
@@ -1457,13 +1620,13 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
- expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
- paths: ['logs/', 'binaries/'],
+ expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq([
+ paths: ['binaries/'],
untracked: true,
key: { files: ['file'] },
policy: 'pull-push',
when: 'on_success'
- )
+ ])
end
it 'returns cache files with prefix' do
@@ -1481,34 +1644,34 @@ module Gitlab
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes('test').size).to eq(1)
- expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq(
+ expect(config_processor.stage_builds_attributes('test').first[:cache]).to eq([
paths: ['logs/', 'binaries/'],
untracked: true,
key: { files: ['file'], prefix: 'prefix' },
policy: 'pull-push',
when: 'on_success'
- )
+ ])
end
it "overwrite cache when defined for a job and globally" do
config = YAML.dump({
- cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
- rspec: {
- script: "rspec",
- cache: { paths: ["test/"], untracked: false, key: 'local' }
- }
- })
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
+ rspec: {
+ script: "rspec",
+ cache: { paths: ["test/"], untracked: false, key: 'local' }
+ }
+ })
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
expect(config_processor.stage_builds_attributes("test").size).to eq(1)
- expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq(
+ expect(config_processor.stage_builds_attributes("test").first[:cache]).to eq([
paths: ["test/"],
untracked: false,
key: 'local',
policy: 'pull-push',
when: 'on_success'
- )
+ ])
end
end
@@ -1926,8 +2089,8 @@ module Gitlab
only: { refs: %w[branches tags] },
options: { script: ["test"] },
needs_attributes: [
- { name: "build1", artifacts: true },
- { name: "build2", artifacts: true }
+ { name: "build1", artifacts: true, optional: false },
+ { name: "build2", artifacts: true, optional: false }
],
when: "on_success",
allow_failure: false,
@@ -1941,7 +2104,7 @@ module Gitlab
let(:needs) do
[
{ job: 'parallel', artifacts: false },
- { job: 'build1', artifacts: true },
+ { job: 'build1', artifacts: true, optional: true },
'build2'
]
end
@@ -1968,10 +2131,10 @@ module Gitlab
only: { refs: %w[branches tags] },
options: { script: ["test"] },
needs_attributes: [
- { name: "parallel 1/2", artifacts: false },
- { name: "parallel 2/2", artifacts: false },
- { name: "build1", artifacts: true },
- { name: "build2", artifacts: true }
+ { name: "parallel 1/2", artifacts: false, optional: false },
+ { name: "parallel 2/2", artifacts: false, optional: false },
+ { name: "build1", artifacts: true, optional: true },
+ { name: "build2", artifacts: true, optional: false }
],
when: "on_success",
allow_failure: false,
@@ -1993,8 +2156,8 @@ module Gitlab
only: { refs: %w[branches tags] },
options: { script: ["test"] },
needs_attributes: [
- { name: "parallel 1/2", artifacts: true },
- { name: "parallel 2/2", artifacts: true }
+ { name: "parallel 1/2", artifacts: true, optional: false },
+ { name: "parallel 2/2", artifacts: true, optional: false }
],
when: "on_success",
allow_failure: false,
@@ -2022,10 +2185,10 @@ module Gitlab
only: { refs: %w[branches tags] },
options: { script: ["test"] },
needs_attributes: [
- { name: "build1", artifacts: true },
- { name: "build2", artifacts: true },
- { name: "parallel 1/2", artifacts: true },
- { name: "parallel 2/2", artifacts: true }
+ { name: "build1", artifacts: true, optional: false },
+ { name: "build2", artifacts: true, optional: false },
+ { name: "parallel 1/2", artifacts: true, optional: false },
+ { name: "parallel 2/2", artifacts: true, optional: false }
],
when: "on_success",
allow_failure: false,