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>2022-12-20 17:22:11 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-12-20 17:22:11 +0300
commit0c872e02b2c822e3397515ec324051ff540f0cd5 (patch)
treece2fb6ce7030e4dad0f4118d21ab6453e5938cdd /spec/lib/gitlab/ci
parentf7e05a6853b12f02911494c4b3fe53d9540d74fc (diff)
Add latest changes from gitlab-org/gitlab@15-7-stable-eev15.7.0-rc42
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r--spec/lib/gitlab/ci/build/cache_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/build/context/build_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/build/hook_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config/entry/artifacts_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb72
-rw-r--r--spec/lib/gitlab/ci/config/entry/default_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/config/entry/hooks_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/config/entry/id_token_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb31
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/config/entry/trigger_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/config/entry/variable_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/base_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/filter_spec.rb32
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/location_expander_spec.rb72
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb74
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/normalizer_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/variables_expander_spec.rb45
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb137
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb51
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb225
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/environment_matcher_spec.rb51
-rw-r--r--spec/lib/gitlab/ci/lint_spec.rb45
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb57
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/logger_spec.rb160
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb1607
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/component_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/report_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/source_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/security/reports_spec.rb101
-rw-r--r--spec/lib/gitlab/ci/runner_instructions_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/trace/archive_spec.rb9
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/yaml_processor/result_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb65
53 files changed, 2188 insertions, 1238 deletions
diff --git a/spec/lib/gitlab/ci/build/cache_spec.rb b/spec/lib/gitlab/ci/build/cache_spec.rb
index 7477aedb994..a8fa14b4b4c 100644
--- a/spec/lib/gitlab/ci/build/cache_spec.rb
+++ b/spec/lib/gitlab/ci/build/cache_spec.rb
@@ -14,8 +14,8 @@ RSpec.describe Gitlab::Ci::Build::Cache do
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(Gitlab::Ci::Pipeline::Seed::Build::Cache).to have_received(:new).with(pipeline, { key: 'key-a' }, 0)
+ expect(Gitlab::Ci::Pipeline::Seed::Build::Cache).to have_received(:new).with(pipeline, { key: 'key-b' }, 1)
expect(cache.instance_variable_get(:@cache)).to eq([cache_seed_a, cache_seed_b])
end
end
@@ -29,7 +29,7 @@ RSpec.describe Gitlab::Ci::Build::Cache do
cache = described_class.new(cache_config, pipeline)
- expect(Gitlab::Ci::Pipeline::Seed::Build::Cache).to have_received(:new).with(pipeline, cache_config)
+ expect(Gitlab::Ci::Pipeline::Seed::Build::Cache).to have_received(:new).with(pipeline, cache_config, 0)
expect(cache.instance_variable_get(:@cache)).to eq([cache_seed])
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 7f862a3b80a..74739a67be0 100644
--- a/spec/lib/gitlab/ci/build/context/build_spec.rb
+++ b/spec/lib/gitlab/ci/build/context/build_spec.rb
@@ -2,11 +2,11 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Build::Context::Build do
+RSpec.describe Gitlab::Ci::Build::Context::Build, feature_category: :pipeline_authoring do
let(:pipeline) { create(:ci_pipeline) }
let(:seed_attributes) { { 'name' => 'some-job' } }
- let(:context) { described_class.new(pipeline, seed_attributes) }
+ subject(:context) { described_class.new(pipeline, seed_attributes) }
shared_examples 'variables collection' do
it { is_expected.to include('CI_COMMIT_REF_NAME' => 'master') }
@@ -22,6 +22,12 @@ RSpec.describe Gitlab::Ci::Build::Context::Build do
it { is_expected.to include('CI_BUILD_REF_NAME' => 'master') }
it { is_expected.to include('CI_PROJECT_PATH' => pipeline.project.full_path) }
end
+
+ context 'when environment:name is provided' do
+ let(:seed_attributes) { { 'name' => 'some-job', 'environment' => 'test' } }
+
+ it { is_expected.to include('CI_ENVIRONMENT_NAME' => 'test') }
+ end
end
describe '#variables' do
diff --git a/spec/lib/gitlab/ci/build/hook_spec.rb b/spec/lib/gitlab/ci/build/hook_spec.rb
new file mode 100644
index 00000000000..6ed40a44c97
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/hook_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Build::Hook, feature_category: :pipeline_authoring do
+ let_it_be(:build1) do
+ FactoryBot.build(:ci_build,
+ options: { hooks: { pre_get_sources_script: ["echo 'hello pre_get_sources_script'"] } })
+ end
+
+ describe '.from_hooks' do
+ subject(:from_hooks) { described_class.from_hooks(build1) }
+
+ it 'initializes and returns hooks' do
+ expect(from_hooks.size).to eq(1)
+ expect(from_hooks[0].name).to eq('pre_get_sources_script')
+ expect(from_hooks[0].script).to eq(["echo 'hello pre_get_sources_script'"])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
index 7476fc6c25f..6264e0c8e33 100644
--- a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
@@ -142,6 +142,26 @@ RSpec.describe Gitlab::Ci::Config::Entry::Artifacts do
end
end
+ context 'when the `when` keyword is not a string' do
+ context 'when it is an array' do
+ let(:config) { { paths: %w[results.txt], when: ['always'] } }
+
+ it 'returns error' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'artifacts when should be a string'
+ end
+ end
+
+ context 'when it is a boolean' do
+ let(:config) { { paths: %w[results.txt], when: true } }
+
+ it 'returns error' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'artifacts when should be a string'
+ end
+ end
+ end
+
describe 'excluded artifacts' do
context 'when configuration is valid' do
let(:config) { { untracked: true, exclude: ['some/directory/'] } }
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index 8da46561b73..736c184a289 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
# that we know that we don't want to inherit
# as they do not have sense in context of Bridge
let(:ignored_inheritable_columns) do
- %i[before_script after_script image services cache interruptible timeout
+ %i[before_script after_script hooks image services cache interruptible timeout
retry tags artifacts]
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 247f4b63910..414cbb169b9 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -163,22 +163,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
end
end
- 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')
- end
- end
-
- 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')
- end
- end
-
context 'when descendants are invalid' do
context 'with invalid keys' do
let(:config) { { key: 1 } }
@@ -228,6 +212,62 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
is_expected.to include 'cache config contains unknown keys: invalid'
end
end
+
+ context 'when the `when` keyword is not a valid string' do
+ context 'when `when` is unknown' do
+ let(:config) { { when: 'unknown' } }
+
+ it 'returns error' do
+ is_expected.to include('cache when should be one of: on_success, on_failure, always')
+ end
+ end
+
+ context 'when it is an array' do
+ let(:config) { { when: ['always'] } }
+
+ it 'returns error' do
+ expect(entry).not_to be_valid
+ is_expected.to include('cache when should be a string')
+ end
+ end
+
+ context 'when it is a boolean' do
+ let(:config) { { when: true } }
+
+ it 'returns error' do
+ expect(entry).not_to be_valid
+ is_expected.to include('cache when should be a string')
+ end
+ end
+ end
+
+ context 'when the `policy` keyword is not a valid string' do
+ context 'when `policy` is unknown' do
+ let(:config) { { policy: 'unknown' } }
+
+ it 'returns error' do
+ is_expected.to include('cache policy should be one of: pull-push, push, pull')
+ end
+ end
+
+ context 'when it is an array' do
+ let(:config) { { policy: ['pull-push'] } }
+
+ it 'returns error' do
+ expect(entry).not_to be_valid
+ is_expected.to include('cache policy should be a string')
+ end
+ end
+
+ context 'when it is a boolean' do
+ let(:config) { { policy: true } }
+
+ it 'returns error' do
+ expect(entry).not_to be_valid
+ is_expected.to include('cache policy should be a string')
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb
index 5613b0f09d1..46e96843ee3 100644
--- a/spec/lib/gitlab/ci/config/entry/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb
@@ -26,9 +26,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Default do
context 'when filtering all the entry/node names' do
it 'contains the expected node names' do
expect(described_class.nodes.keys)
- .to match_array(%i[before_script image services
- after_script cache interruptible
- timeout retry tags artifacts])
+ .to match_array(%i[before_script after_script hooks cache image services
+ interruptible timeout retry tags artifacts])
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/hooks_spec.rb b/spec/lib/gitlab/ci/config/entry/hooks_spec.rb
new file mode 100644
index 00000000000..7a5ff244e18
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/hooks_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+RSpec.describe Gitlab::Ci::Config::Entry::Hooks do
+ subject(:entry) { described_class.new(config) }
+
+ before do
+ entry.compose!
+ end
+
+ describe 'validations' do
+ context 'when passing a valid hook' do
+ let(:config) { { pre_get_sources_script: ['ls'] } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when passing an invalid hook' do
+ let(:config) { { x_get_something: ['ls'] } }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when entry config is not a hash' do
+ let(:config) { 'ls' }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe '#value' do
+ let(:config) { { pre_get_sources_script: ['ls'] } }
+
+ it 'returns a hash' do
+ expect(entry.value).to eq(config)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/id_token_spec.rb b/spec/lib/gitlab/ci/config/entry/id_token_spec.rb
new file mode 100644
index 00000000000..12585d662ec
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/id_token_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Entry::IdToken do
+ context 'when given `aud` as a string' do
+ it 'is valid' do
+ config = { aud: 'https://gitlab.com' }
+ id_token = described_class.new(config)
+
+ id_token.compose!
+
+ expect(id_token).to be_valid
+ expect(id_token.value).to eq(aud: 'https://gitlab.com')
+ end
+ end
+
+ context 'when given `aud` as an array' do
+ it 'is valid and concatenates the values' do
+ config = { aud: ['https://gitlab.com', 'https://aws.com'] }
+ id_token = described_class.new(config)
+
+ id_token.compose!
+
+ expect(id_token).to be_valid
+ expect(id_token.value).to eq(aud: ['https://gitlab.com', 'https://aws.com'])
+ end
+ end
+
+ context 'when not given an `aud`' do
+ it 'is invalid' do
+ config = {}
+ id_token = described_class.new(config)
+
+ id_token.compose!
+
+ expect(id_token).not_to be_valid
+ expect(id_token.errors).to match_array([
+ 'id token config missing required keys: aud',
+ 'id token aud should be an array of strings or a string'
+ ])
+ end
+ end
+
+ context 'when given an unknown keyword' do
+ it 'is invalid' do
+ config = { aud: 'https://gitlab.com', unknown: 'test' }
+ id_token = described_class.new(config)
+
+ id_token.compose!
+
+ expect(id_token).not_to be_valid
+ expect(id_token.errors).to match_array([
+ 'id token config contains unknown keys: unknown'
+ ])
+ 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 acf60a6cdda..becb46ac2e7 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
subject { described_class.nodes.keys }
let(:result) do
- %i[before_script script stage after_script cache
+ %i[before_script script after_script hooks stage cache
image services only except rules needs variables artifacts
environment coverage retry interruptible timeout release tags
inherit parallel]
@@ -716,7 +716,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
let(:config) do
{ before_script: %w[ls pwd],
script: 'rspec',
- after_script: %w[cleanup] }
+ after_script: %w[cleanup],
+ id_tokens: { TEST_ID_TOKEN: { aud: 'https://gitlab.com' } },
+ hooks: { pre_get_sources_script: 'echo hello' } }
end
it 'returns correct value' do
@@ -727,10 +729,33 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
stage: 'test',
ignore: false,
after_script: %w[cleanup],
+ hooks: { pre_get_sources_script: ['echo hello'] },
only: { refs: %w[branches tags] },
job_variables: {},
root_variables_inheritance: true,
- scheduling_type: :stage)
+ scheduling_type: :stage,
+ id_tokens: { TEST_ID_TOKEN: { aud: 'https://gitlab.com' } })
+ end
+
+ context 'when the FF ci_hooks_pre_get_sources_script is disabled' do
+ before do
+ stub_feature_flags(ci_hooks_pre_get_sources_script: false)
+ end
+
+ it 'returns correct value' do
+ expect(entry.value)
+ .to eq(name: :rspec,
+ before_script: %w[ls pwd],
+ script: %w[rspec],
+ stage: 'test',
+ ignore: false,
+ after_script: %w[cleanup],
+ only: { refs: %w[branches tags] },
+ job_variables: {},
+ root_variables_inheritance: true,
+ scheduling_type: :stage,
+ id_tokens: { TEST_ID_TOKEN: { aud: 'https://gitlab.com' } })
+ 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 085293d7368..c40589104cd 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -5,7 +5,8 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Root do
let(:user) {}
let(:project) {}
- let(:root) { described_class.new(hash, user: user, project: project) }
+ let(:logger) { Gitlab::Ci::Pipeline::Logger.new(project: project) }
+ let(:root) { described_class.new(hash, user: user, project: project, logger: logger) }
describe '.nodes' do
it 'returns a hash' do
@@ -37,7 +38,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
variables: {
VAR: 'root',
VAR2: { value: 'val 2', description: 'this is var 2' },
- VAR3: { value: %w[val3 val3b], description: 'this is var 3' }
+ VAR3: { value: 'val3', options: %w[val3 val4 val5], description: 'this is var 3 and some options' }
},
after_script: ['make clean'],
stages: %w(build pages release),
@@ -228,6 +229,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
)
end
end
+
+ it 'tracks log entries' do
+ expect(logger.observations_hash).to match(
+ a_hash_including(
+ 'config_root_compose_jobs_factory_duration_s' => a_kind_of(Numeric)
+ )
+ )
+ end
end
end
@@ -317,6 +326,42 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
end
end
+ context 'when variables have `options` data' do
+ before do
+ root.compose!
+ end
+
+ context 'and the value is in the `options` array' do
+ let(:hash) do
+ {
+ variables: { 'VAR' => { value: 'val1', options: %w[val1 val2] } },
+ rspec: { script: 'bin/rspec' }
+ }
+ end
+
+ it 'returns correct value' do
+ expect(root.variables_entry.value_with_data).to eq(
+ 'VAR' => { value: 'val1' }
+ )
+
+ expect(root.variables_value).to eq('VAR' => 'val1')
+ end
+ end
+
+ context 'and the value is not in the `options` array' do
+ let(:hash) do
+ {
+ variables: { 'VAR' => { value: 'val', options: %w[val1 val2] } },
+ rspec: { script: 'bin/rspec' }
+ }
+ end
+
+ it 'returns an error' do
+ expect(root.errors).to contain_exactly('variables:var config value must be present in options')
+ end
+ end
+ end
+
context 'when variables have "expand" data' do
let(:hash) do
{
diff --git a/spec/lib/gitlab/ci/config/entry/trigger_spec.rb b/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
index d0116c961d7..f47923af45a 100644
--- a/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/trigger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::Entry::Trigger do
+RSpec.describe Gitlab::Ci::Config::Entry::Trigger, feature_category: :pipeline_authoring do
subject { described_class.new(config) }
context 'when trigger config is a non-empty string' do
@@ -35,6 +35,48 @@ RSpec.describe Gitlab::Ci::Config::Entry::Trigger do
end
context 'when trigger is a hash - cross-project' do
+ context 'when project is a string' do
+ context 'when project is a non-empty string' do
+ let(:config) { { project: 'some/project' } }
+
+ it 'is valid' do
+ expect(subject).to be_valid
+ end
+ end
+
+ context 'when project is an empty string' do
+ let(:config) { { project: '' } }
+
+ it 'returns error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first)
+ .to match /project can't be blank/
+ end
+ end
+ end
+
+ context 'when project is not a string' do
+ context 'when project is an array' do
+ let(:config) { { project: ['some/project'] } }
+
+ it 'returns error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first)
+ .to match /should be a string/
+ end
+ end
+
+ context 'when project is a boolean' do
+ let(:config) { { project: true } }
+
+ it 'returns error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first)
+ .to match /should be a string/
+ end
+ end
+ end
+
context 'when branch is provided' do
let(:config) { { project: 'some/project', branch: 'feature' } }
diff --git a/spec/lib/gitlab/ci/config/entry/variable_spec.rb b/spec/lib/gitlab/ci/config/entry/variable_spec.rb
index d7023072312..97b06c8b1a5 100644
--- a/spec/lib/gitlab/ci/config/entry/variable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variable_spec.rb
@@ -306,48 +306,48 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variable do
end
end
end
- end
- describe 'ComplexArrayVariable' do
- context 'when allow_array_value metadata is false' do
- let(:config) { { value: %w[value value2], description: 'description' } }
- let(:metadata) { { allow_array_value: false } }
+ context 'when config is a hash with options' do
+ context 'when there is no metadata' do
+ let(:config) { { value: 'value', options: %w[value value2] } }
+ let(:metadata) { {} }
- describe '#valid?' do
- it { is_expected.not_to be_valid }
- end
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
- describe '#errors' do
- subject(:errors) { entry.errors }
+ describe '#errors' do
+ subject(:errors) { entry.errors }
- it { is_expected.to include 'var1 config value must be an alphanumeric string' }
+ it { is_expected.to include 'var1 config must be a string' }
+ end
end
- end
- context 'when allow_array_value metadata is true' do
- let(:config) { { value: %w[value value2], description: 'description' } }
- let(:metadata) { { allowed_value_data: %i[value description], allow_array_value: true } }
+ context 'when options are allowed' do
+ let(:config) { { value: 'value', options: %w[value value2] } }
+ let(:metadata) { { allowed_value_data: %i[value options] } }
- describe '#valid?' do
- it { is_expected.to be_valid }
- end
+ describe '#valid?' do
+ it { is_expected.to be_valid }
+ end
- describe '#value' do
- subject(:value) { entry.value }
+ describe '#value' do
+ subject(:value) { entry.value }
- it { is_expected.to eq('value') }
- end
+ it { is_expected.to eq('value') }
+ end
- describe '#value_with_data' do
- subject(:value_with_data) { entry.value_with_data }
+ describe '#value_with_data' do
+ subject(:value_with_data) { entry.value_with_data }
- it { is_expected.to eq(value: 'value') }
- end
+ it { is_expected.to eq(value: 'value') }
+ end
- describe '#value_with_prefill_data' do
- subject(:value_with_prefill_data) { entry.value_with_prefill_data }
+ describe '#value_with_prefill_data' do
+ subject(:value_with_prefill_data) { entry.value_with_prefill_data }
- it { is_expected.to eq(value: 'value', description: 'description', value_options: %w[value value2]) }
+ it { is_expected.to eq(value: 'value', options: %w[value value2]) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index 609e4422d5c..e7dbc78729d 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -116,8 +116,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
it_behaves_like 'invalid config', /variable_1 config must be a string/
end
- context 'when metadata has allow_array_value and allowed_value_data' do
- let(:metadata) { { allowed_value_data: %i[value description], allow_array_value: true } }
+ context 'when metadata has the allowed_value_data key' do
+ let(:metadata) { { allowed_value_data: %i[value description options] } }
let(:result) do
{ 'VARIABLE_1' => 'value' }
@@ -143,17 +143,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
end
end
- context 'when entry config value has key-value pair and value is an array' do
+ context 'when entry config value has options' do
let(:config) do
- { 'VARIABLE_1' => { value: %w[value1 value2], description: 'variable 1' } }
+ { 'VARIABLE_1' => {
+ value: 'value1', options: %w[value1 value2], description: 'variable 1'
+ } }
end
- context 'when there is no allowed_value_data metadata' do
- it_behaves_like 'invalid config', /variable_1 config value must be an alphanumeric string/
- end
-
- context 'when metadata has allow_array_value and allowed_value_data' do
- let(:metadata) { { allowed_value_data: %i[value description], allow_array_value: true } }
+ context 'when metadata has allowed_value_data' do
+ let(:metadata) { { allowed_value_data: %i[value description options] } }
let(:result) do
{ 'VARIABLE_1' => 'value1' }
@@ -172,7 +170,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
describe '#value_with_prefill_data' do
it 'returns variable with prefill data' do
expect(entry.value_with_prefill_data).to eq(
- 'VARIABLE_1' => { value: 'value1', value_options: %w[value1 value2], description: 'variable 1' }
+ 'VARIABLE_1' => { value: 'value1', options: %w[value1 value2], description: 'variable 1' }
)
end
end
@@ -234,14 +232,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
it_behaves_like 'invalid config', /variable_1 config uses invalid data keys: hello/
end
- context 'when entry config value has hash with nil description' do
- let(:config) do
- { 'VARIABLE_1' => { value: 'value 1', description: nil } }
- end
-
- it_behaves_like 'invalid config', /variable_1 config description must be an alphanumeric string/
- end
-
context 'when entry config value has hash without description' do
let(:config) do
{ 'VARIABLE_1' => { value: 'value 1' } }
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index c22afb32756..8d93cdcf378 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -188,6 +188,19 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
'is blocked: Requests to localhost are not allowed!'
end
end
+
+ context 'when connection refused error has been raised' do
+ let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' }
+ let(:exception) { Errno::ECONNREFUSED.new }
+
+ before do
+ stub_full_request(location).to_raise(exception)
+ end
+
+ it 'returns details about connection failure' do
+ expect(subject).to eq "Remote file could not be fetched because Connection refused!"
+ end
+ end
end
describe '#expand_context' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper/base_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/base_spec.rb
new file mode 100644
index 00000000000..0fdcc5e8ff7
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/mapper/base_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::Mapper::Base, feature_category: :pipeline_authoring do
+ let(:test_class) do
+ Class.new(described_class) do
+ def self.name
+ 'TestClass'
+ end
+ end
+ end
+
+ let(:context) { Gitlab::Ci::Config::External::Context.new }
+ let(:mapper) { test_class.new(context) }
+
+ describe '#process' do
+ subject(:process) { mapper.process }
+
+ context 'when the method is not implemented' do
+ it 'raises NotImplementedError' do
+ expect { process }.to raise_error(NotImplementedError)
+ end
+ end
+
+ context 'when the method is implemented' do
+ before do
+ test_class.class_eval do
+ def process_without_instrumentation
+ 'test'
+ end
+ end
+ end
+
+ it 'calls the method' do
+ expect(process).to eq('test')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/mapper/filter_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/filter_spec.rb
new file mode 100644
index 00000000000..df2a2f0fd01
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/mapper/filter_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::Mapper::Filter, feature_category: :pipeline_authoring do
+ let_it_be(:variables) do
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'VARIABLE1', value: 'hello')
+ end
+ end
+
+ let_it_be(:context) do
+ Gitlab::Ci::Config::External::Context.new(variables: variables)
+ end
+
+ subject(:filter) { described_class.new(context) }
+
+ describe '#process' do
+ let(:locations) do
+ [{ local: 'config/.gitlab-ci.yml', rules: [{ if: '$VARIABLE1' }] },
+ { remote: 'https://example.com/.gitlab-ci.yml', rules: [{ if: '$VARIABLE2' }] }]
+ end
+
+ subject(:process) { filter.process(locations) }
+
+ it 'filters locations according to rules' do
+ is_expected.to eq(
+ [{ local: 'config/.gitlab-ci.yml', rules: [{ if: '$VARIABLE1' }] }]
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/mapper/location_expander_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/location_expander_spec.rb
new file mode 100644
index 00000000000..b14b6b0ca29
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/mapper/location_expander_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::Mapper::LocationExpander, feature_category: :pipeline_authoring do
+ include RepoHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { project.owner }
+
+ let(:sha) { project.commit.sha }
+
+ let(:context) do
+ Gitlab::Ci::Config::External::Context.new(project: project, user: user, sha: sha)
+ end
+
+ subject(:location_expander) { described_class.new(context) }
+
+ describe '#process' do
+ subject(:process) { location_expander.process(locations) }
+
+ context 'when there are project files' do
+ let(:locations) do
+ [{ project: 'gitlab-org/gitlab-1', file: ['builds.yml', 'tests.yml'] },
+ { project: 'gitlab-org/gitlab-2', file: 'deploy.yml' }]
+ end
+
+ it 'returns expanded locations' do
+ is_expected.to eq(
+ [{ project: 'gitlab-org/gitlab-1', file: 'builds.yml' },
+ { project: 'gitlab-org/gitlab-1', file: 'tests.yml' },
+ { project: 'gitlab-org/gitlab-2', file: 'deploy.yml' }]
+ )
+ end
+ end
+
+ context 'when there are local files' do
+ let(:locations) do
+ [{ local: 'builds/*.yml' },
+ { local: 'tests.yml' }]
+ end
+
+ let(:project_files) do
+ { 'builds/1.yml' => 'a', 'builds/2.yml' => 'b', 'tests.yml' => 'c' }
+ end
+
+ around do |example|
+ create_and_delete_files(project, project_files) do
+ example.run
+ end
+ end
+
+ it 'returns expanded locations' do
+ is_expected.to eq(
+ [{ local: 'builds/1.yml' },
+ { local: 'builds/2.yml' },
+ { local: 'tests.yml' }]
+ )
+ end
+ end
+
+ context 'when there are other files' do
+ let(:locations) do
+ [{ remote: 'https://gitlab.com/gitlab-org/gitlab-ce/raw/master/.gitlab-ci.yml' }]
+ end
+
+ it 'returns the same location' do
+ is_expected.to eq(locations)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
new file mode 100644
index 00000000000..5f321a696c9
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category: :pipeline_authoring do
+ let_it_be(:variables) do
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'A_MASKED_VAR', value: 'this-is-secret', masked: true)
+ end
+ end
+
+ let_it_be(:context) do
+ Gitlab::Ci::Config::External::Context.new(variables: variables)
+ end
+
+ subject(:matcher) { described_class.new(context) }
+
+ describe '#process' do
+ let(:locations) do
+ [{ local: 'file.yml' },
+ { file: 'file.yml', project: 'namespace/project' },
+ { remote: 'https://example.com/.gitlab-ci.yml' },
+ { template: 'file.yml' },
+ { artifact: 'generated.yml', job: 'test' }]
+ end
+
+ subject(:process) { matcher.process(locations) }
+
+ it 'returns an array of file objects' do
+ is_expected.to contain_exactly(
+ an_instance_of(Gitlab::Ci::Config::External::File::Local),
+ an_instance_of(Gitlab::Ci::Config::External::File::Project),
+ an_instance_of(Gitlab::Ci::Config::External::File::Remote),
+ an_instance_of(Gitlab::Ci::Config::External::File::Template),
+ an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
+ )
+ end
+
+ context 'when a location is not valid' do
+ let(:locations) { [{ invalid: 'file.yml' }] }
+
+ it 'raises an error' do
+ expect { process }.to raise_error(
+ Gitlab::Ci::Config::External::Mapper::AmbigiousSpecificationError,
+ '`{"invalid":"file.yml"}` does not have a valid subkey for include. ' \
+ 'Valid subkeys are: `local`, `project`, `remote`, `template`, `artifact`'
+ )
+ end
+
+ context 'when the invalid location includes a masked variable' do
+ let(:locations) { [{ invalid: 'this-is-secret.yml' }] }
+
+ it 'raises an error with a masked sentence' do
+ expect { process }.to raise_error(
+ Gitlab::Ci::Config::External::Mapper::AmbigiousSpecificationError,
+ '`{"invalid":"xxxxxxxxxxxxxx.yml"}` does not have a valid subkey for include. ' \
+ 'Valid subkeys are: `local`, `project`, `remote`, `template`, `artifact`'
+ )
+ end
+ end
+ end
+
+ context 'when a location is ambiguous' do
+ let(:locations) { [{ local: 'file.yml', remote: 'https://example.com/.gitlab-ci.yml' }] }
+
+ it 'raises an error' do
+ expect { process }.to raise_error(
+ Gitlab::Ci::Config::External::Mapper::AmbigiousSpecificationError,
+ "Each include must use only one of: `local`, `project`, `remote`, `template`, `artifact`"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/mapper/normalizer_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/normalizer_spec.rb
new file mode 100644
index 00000000000..709c234253b
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/mapper/normalizer_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::Mapper::Normalizer, feature_category: :pipeline_authoring do
+ let_it_be(:variables) do
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'VARIABLE1', value: 'config')
+ variables.append(key: 'VARIABLE2', value: 'https://example.com')
+ end
+ end
+
+ let_it_be(:context) do
+ Gitlab::Ci::Config::External::Context.new(variables: variables)
+ end
+
+ subject(:normalizer) { described_class.new(context) }
+
+ describe '#process' do
+ let(:locations) do
+ ['https://example.com/.gitlab-ci.yml',
+ 'config/.gitlab-ci.yml',
+ { local: 'config/.gitlab-ci.yml' },
+ { remote: 'https://example.com/.gitlab-ci.yml' },
+ { template: 'Template.gitlab-ci.yml' },
+ '$VARIABLE1/.gitlab-ci.yml',
+ '$VARIABLE2/.gitlab-ci.yml']
+ end
+
+ subject(:process) { normalizer.process(locations) }
+
+ it 'converts locations to canonical form' do
+ is_expected.to eq(
+ [{ remote: 'https://example.com/.gitlab-ci.yml' },
+ { local: 'config/.gitlab-ci.yml' },
+ { local: 'config/.gitlab-ci.yml' },
+ { remote: 'https://example.com/.gitlab-ci.yml' },
+ { template: 'Template.gitlab-ci.yml' },
+ { local: 'config/.gitlab-ci.yml' },
+ { remote: 'https://example.com/.gitlab-ci.yml' }]
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/mapper/variables_expander_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/variables_expander_spec.rb
new file mode 100644
index 00000000000..f7454dcd4be
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/mapper/variables_expander_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::Mapper::VariablesExpander, feature_category: :pipeline_authoring do
+ let_it_be(:variables) do
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'VARIABLE1', value: 'hello')
+ end
+ end
+
+ let_it_be(:context) do
+ Gitlab::Ci::Config::External::Context.new(variables: variables)
+ end
+
+ subject(:variables_expander) { described_class.new(context) }
+
+ describe '#process' do
+ subject(:process) { variables_expander.process(locations) }
+
+ context 'when locations are strings' do
+ let(:locations) { ['$VARIABLE1.gitlab-ci.yml'] }
+
+ it 'expands variables' do
+ is_expected.to eq(['hello.gitlab-ci.yml'])
+ end
+ end
+
+ context 'when locations are hashes' do
+ let(:locations) { [{ local: '$VARIABLE1.gitlab-ci.yml' }] }
+
+ it 'expands variables' do
+ is_expected.to eq([{ local: 'hello.gitlab-ci.yml' }])
+ end
+ end
+
+ context 'when locations are arrays' do
+ let(:locations) { [{ local: ['$VARIABLE1.gitlab-ci.yml'] }] }
+
+ it 'expands variables' do
+ is_expected.to eq([{ local: ['hello.gitlab-ci.yml'] }])
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
new file mode 100644
index 00000000000..7c7252c6b0e
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category: :pipeline_authoring do
+ include RepoHelpers
+ include StubRequests
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { project.owner }
+
+ let(:context) do
+ Gitlab::Ci::Config::External::Context.new(project: project, user: user, sha: project.commit.id)
+ end
+
+ let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
+
+ let(:project_files) do
+ {
+ 'myfolder/file1.yml' => <<~YAML,
+ my_build:
+ script: echo Hello World
+ YAML
+ 'myfolder/file2.yml' => <<~YAML,
+ my_test:
+ script: echo Hello World
+ YAML
+ 'nested_configs.yml' => <<~YAML
+ include:
+ - local: myfolder/file1.yml
+ - local: myfolder/file2.yml
+ - remote: #{remote_url}
+ YAML
+ }
+ end
+
+ around(:all) do |example|
+ create_and_delete_files(project, project_files) do
+ example.run
+ end
+ end
+
+ before do
+ stub_full_request(remote_url).to_return(
+ body: <<~YAML
+ remote_test:
+ script: echo Hello World
+ YAML
+ )
+ end
+
+ subject(:verifier) { described_class.new(context) }
+
+ describe '#process' do
+ subject(:process) { verifier.process(files) }
+
+ context 'when files are local' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context)
+ ]
+ end
+
+ it 'returns an array of file objects' do
+ expect(process.map(&:location)).to contain_exactly('myfolder/file1.yml', 'myfolder/file2.yml')
+ end
+
+ it 'adds files to the expandset' do
+ expect { process }.to change { context.expandset.count }.by(2)
+ end
+ end
+
+ context 'when a file includes other files' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'nested_configs.yml' }, context)
+ ]
+ end
+
+ it 'returns an array of file objects with combined hash' do
+ expect(process.map(&:to_hash)).to contain_exactly(
+ { my_build: { script: 'echo Hello World' },
+ my_test: { script: 'echo Hello World' },
+ remote_test: { script: 'echo Hello World' } }
+ )
+ end
+ end
+
+ context 'when there is an invalid file' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/invalid.yml' }, context)
+ ]
+ end
+
+ it 'adds an error to the file' do
+ expect(process.first.errors).to include("Local file `myfolder/invalid.yml` does not exist!")
+ end
+ end
+
+ context 'when max_includes is exceeded' do
+ context 'when files are nested' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'nested_configs.yml' }, context)
+ ]
+ end
+
+ before do
+ allow(context).to receive(:max_includes).and_return(1)
+ end
+
+ it 'raises Processor::IncludeError' do
+ expect { process }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError)
+ end
+ end
+
+ context 'when files are not nested' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context)
+ ]
+ end
+
+ before do
+ allow(context).to receive(:max_includes).and_return(1)
+ end
+
+ it 'raises Mapper::TooManyIncludesError' do
+ expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index d905568f01e..b7e58d4dfa1 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -2,8 +2,10 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::Mapper do
+# This will be removed with FF ci_refactoring_external_mapper and moved to below.
+RSpec.shared_context 'gitlab_ci_config_external_mapper' do
include StubRequests
+ include RepoHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
@@ -12,13 +14,13 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
let(:variables) { project.predefined_variables }
- let(:context_params) { { project: project, sha: '123456', user: user, variables: variables } }
+ let(:context_params) { { project: project, sha: project.commit.sha, user: user, variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:file_content) do
- <<~HEREDOC
+ <<~YAML
image: 'image:1.0'
- HEREDOC
+ YAML
end
subject(:mapper) { described_class.new(values, context) }
@@ -38,7 +40,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
it 'propagates the pipeline logger' do
process
- fetch_content_log_count = mapper
+ fetch_content_log_count = context
.logger
.observations_hash
.dig(key, 'count')
@@ -231,7 +233,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
it 'has expanset with one' do
process
- expect(mapper.expandset.size).to eq(1)
+ expect(context.expandset.size).to eq(1)
end
end
@@ -379,17 +381,28 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
end
context 'when local file path has wildcard' do
- let(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
let(:values) do
{ include: 'myfolder/*.yml' }
end
- before do
- allow_next_instance_of(Repository) do |repository|
- allow(repository).to receive(:search_files_by_wildcard_path).with('myfolder/*.yml', '123456') do
- ['myfolder/file1.yml', 'myfolder/file2.yml']
- end
+ let(:project_files) do
+ {
+ 'myfolder/file1.yml' => <<~YAML,
+ my_build:
+ script: echo Hello World
+ YAML
+ 'myfolder/file2.yml' => <<~YAML
+ my_test:
+ script: echo Hello World
+ YAML
+ }
+ end
+
+ around do |example|
+ create_and_delete_files(project, project_files) do
+ example.run
end
end
@@ -445,8 +458,20 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
it 'has expanset with two' do
process
- expect(mapper.expandset.size).to eq(2)
+ expect(context.expandset.size).to eq(2)
end
end
end
end
+
+RSpec.describe Gitlab::Ci::Config::External::Mapper, feature_category: :pipeline_authoring do
+ it_behaves_like 'gitlab_ci_config_external_mapper'
+
+ context 'when the FF ci_refactoring_external_mapper is disabled' do
+ before do
+ stub_feature_flags(ci_refactoring_external_mapper: false)
+ end
+
+ it_behaves_like 'gitlab_ci_config_external_mapper'
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index b1dff6f9723..c9efaf2e1af 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -2,17 +2,31 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::Processor do
+RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipeline_authoring do
include StubRequests
+ include RepoHelpers
- let_it_be(:project) { create(:project, :repository) }
- let_it_be_with_reload(:another_project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:sha) { '12345' }
+ let_it_be_with_reload(:project) { create(:project, :repository) }
+ let_it_be_with_reload(:another_project) { create(:project, :repository) }
+
+ let(:project_files) { {} }
+ let(:other_project_files) { {} }
+
+ let(:sha) { project.commit.sha }
let(:context_params) { { project: project, sha: sha, user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
- let(:processor) { described_class.new(values, context) }
+
+ subject(:processor) { described_class.new(values, context) }
+
+ around do |example|
+ create_and_delete_files(project, project_files) do
+ create_and_delete_files(another_project, other_project_files) do
+ example.run
+ end
+ end
+ end
before do
project.add_developer(user)
@@ -63,7 +77,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:values) { { include: remote_file, image: 'image:1.0' } }
let(:external_file_content) do
- <<-HEREDOC
+ <<-YAML
before_script:
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
- ruby -v
@@ -77,7 +91,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
rubocop:
script:
- bundle exec rubocop
- HEREDOC
+ YAML
end
before do
@@ -98,7 +112,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:values) { { include: remote_file, image: 'image:1.0' } }
let(:external_file_content) do
- <<-HEREDOC
+ <<-YAML
include:
- local: another-file.yml
rules:
@@ -107,7 +121,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
rspec:
script:
- bundle exec rspec
- HEREDOC
+ YAML
end
before do
@@ -127,19 +141,16 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
context 'with a valid local external file is defined' do
let(:values) { { include: '/lib/gitlab/ci/templates/template.yml', image: 'image:1.0' } }
let(:local_file_content) do
- <<-HEREDOC
+ <<-YAML
before_script:
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
- ruby -v
- which ruby
- bundle install --jobs $(nproc) "${FLAGS[@]}"
- HEREDOC
+ YAML
end
- before do
- allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
- .to receive(:fetch_local_content).and_return(local_file_content)
- end
+ let(:project_files) { { '/lib/gitlab/ci/templates/template.yml' => local_file_content } }
it 'appends the file to the values' do
output = processor.perform
@@ -153,6 +164,11 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
context 'with multiple external files are defined' do
let(:remote_file) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
+
+ let(:local_file_content) do
+ File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml'))
+ end
+
let(:external_files) do
[
'/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml',
@@ -168,20 +184,21 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
end
let(:remote_file_content) do
- <<-HEREDOC
+ <<-YAML
stages:
- build
- review
- cleanup
- HEREDOC
+ YAML
end
- before do
- local_file_content = File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml'))
-
- allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
- .to receive(:fetch_local_content).and_return(local_file_content)
+ let(:project_files) do
+ {
+ '/spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml' => local_file_content
+ }
+ end
+ before do
stub_full_request(remote_file).to_return(body: remote_file_content)
end
@@ -199,10 +216,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
let(:local_file_content) { 'invalid content file ////' }
- before do
- allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
- .to receive(:fetch_local_content).and_return(local_file_content)
- end
+ let(:project_files) { { '/lib/gitlab/ci/templates/template.yml' => local_file_content } }
it 'raises an error' do
expect { processor.perform }.to raise_error(
@@ -222,9 +236,9 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
end
let(:remote_file_content) do
- <<~HEREDOC
+ <<~YAML
image: php:5-fpm-alpine
- HEREDOC
+ YAML
end
it 'takes precedence' do
@@ -244,31 +258,32 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
}
end
- before do
- allow(project.repository).to receive(:blob_data_at).with('12345', '/local/file.yml') do
- <<~HEREDOC
- include:
- - template: Ruby.gitlab-ci.yml
- - remote: http://my.domain.com/config.yml
- - project: #{another_project.full_path}
- file: /templates/my-workflow.yml
- HEREDOC
- end
-
- allow_any_instance_of(Repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-workflow.yml') do
- <<~HEREDOC
- include:
- - local: /templates/my-build.yml
- HEREDOC
- end
+ let(:project_files) do
+ {
+ '/local/file.yml' => <<~YAML
+ include:
+ - template: Ruby.gitlab-ci.yml
+ - remote: http://my.domain.com/config.yml
+ - project: #{another_project.full_path}
+ file: /templates/my-workflow.yml
+ YAML
+ }
+ end
- allow_any_instance_of(Repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-build.yml') do
- <<~HEREDOC
- my_build:
- script: echo Hello World
- HEREDOC
- end
+ let(:other_project_files) do
+ {
+ '/templates/my-workflow.yml' => <<~YAML,
+ include:
+ - local: /templates/my-build.yml
+ YAML
+ '/templates/my-build.yml' => <<~YAML
+ my_build:
+ script: echo Hello World
+ YAML
+ }
+ end
+ before do
stub_full_request('http://my.domain.com/config.yml')
.to_return(body: 'remote_build: { script: echo Hello World }')
end
@@ -299,32 +314,32 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
expect(context.includes).to contain_exactly(
{ type: :local,
location: '/local/file.yml',
- blob: "http://localhost/#{project.full_path}/-/blob/12345/local/file.yml",
- raw: "http://localhost/#{project.full_path}/-/raw/12345/local/file.yml",
+ blob: "http://localhost/#{project.full_path}/-/blob/#{sha}/local/file.yml",
+ raw: "http://localhost/#{project.full_path}/-/raw/#{sha}/local/file.yml",
extra: {},
context_project: project.full_path,
- context_sha: '12345' },
+ context_sha: sha },
{ type: :template,
location: 'Ruby.gitlab-ci.yml',
blob: nil,
raw: 'https://gitlab.com/gitlab-org/gitlab/-/raw/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml',
extra: {},
context_project: project.full_path,
- context_sha: '12345' },
+ context_sha: sha },
{ type: :remote,
location: 'http://my.domain.com/config.yml',
blob: nil,
raw: "http://my.domain.com/config.yml",
extra: {},
context_project: project.full_path,
- context_sha: '12345' },
+ context_sha: sha },
{ type: :file,
location: '/templates/my-workflow.yml',
blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-workflow.yml",
raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-workflow.yml",
extra: { project: another_project.full_path, ref: 'HEAD' },
context_project: project.full_path,
- context_sha: '12345' },
+ context_sha: sha },
{ type: :local,
location: '/templates/my-build.yml',
blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-build.yml",
@@ -393,17 +408,17 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
}
end
+ let(:other_project_files) do
+ {
+ '/templates/my-build.yml' => <<~YAML
+ my_build:
+ script: echo Hello World
+ YAML
+ }
+ end
+
before do
another_project.add_developer(user)
-
- allow_next_instance_of(Repository) do |repository|
- allow(repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-build.yml') do
- <<~HEREDOC
- my_build:
- script: echo Hello World
- HEREDOC
- end
- end
end
it 'appends the file to the values' do
@@ -423,24 +438,21 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
}
end
+ let(:other_project_files) do
+ {
+ '/templates/my-build.yml' => <<~YAML,
+ my_build:
+ script: echo Hello World
+ YAML
+ '/templates/my-test.yml' => <<~YAML
+ my_test:
+ script: echo Hello World
+ YAML
+ }
+ end
+
before do
another_project.add_developer(user)
-
- allow_next_instance_of(Repository) do |repository|
- allow(repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-build.yml') do
- <<~HEREDOC
- my_build:
- script: echo Hello World
- HEREDOC
- end
-
- allow(repository).to receive(:blob_data_at).with(another_project.commit.id, '/templates/my-test.yml') do
- <<~HEREDOC
- my_test:
- script: echo Hello World
- HEREDOC
- end
- end
end
it 'appends the file to the values' do
@@ -458,45 +470,34 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-build.yml",
extra: { project: another_project.full_path, ref: 'HEAD' },
context_project: project.full_path,
- context_sha: '12345' },
+ context_sha: sha },
{ type: :file,
blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-test.yml",
raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-test.yml",
location: '/templates/my-test.yml',
extra: { project: another_project.full_path, ref: 'HEAD' },
context_project: project.full_path,
- context_sha: '12345' }
+ context_sha: sha }
)
end
end
context 'when local file path has wildcard' do
- let(:project) { create(:project, :repository) }
-
let(:values) do
{ include: 'myfolder/*.yml', image: 'image:1.0' }
end
- before do
- allow_next_instance_of(Repository) do |repository|
- allow(repository).to receive(:search_files_by_wildcard_path).with('myfolder/*.yml', sha) do
- ['myfolder/file1.yml', 'myfolder/file2.yml']
- end
-
- allow(repository).to receive(:blob_data_at).with(sha, 'myfolder/file1.yml') do
- <<~HEREDOC
- my_build:
- script: echo Hello World
- HEREDOC
- end
-
- allow(repository).to receive(:blob_data_at).with(sha, 'myfolder/file2.yml') do
- <<~HEREDOC
- my_test:
- script: echo Hello World
- HEREDOC
- end
- end
+ let(:project_files) do
+ {
+ 'myfolder/file1.yml' => <<~YAML,
+ my_build:
+ script: echo Hello World
+ YAML
+ 'myfolder/file2.yml' => <<~YAML
+ my_test:
+ script: echo Hello World
+ YAML
+ }
end
it 'fetches the matched files' do
@@ -510,18 +511,18 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
expect(context.includes).to contain_exactly(
{ type: :local,
location: 'myfolder/file1.yml',
- blob: "http://localhost/#{project.full_path}/-/blob/12345/myfolder/file1.yml",
- raw: "http://localhost/#{project.full_path}/-/raw/12345/myfolder/file1.yml",
+ blob: "http://localhost/#{project.full_path}/-/blob/#{sha}/myfolder/file1.yml",
+ raw: "http://localhost/#{project.full_path}/-/raw/#{sha}/myfolder/file1.yml",
extra: {},
context_project: project.full_path,
- context_sha: '12345' },
+ context_sha: sha },
{ type: :local,
- blob: "http://localhost/#{project.full_path}/-/blob/12345/myfolder/file2.yml",
- raw: "http://localhost/#{project.full_path}/-/raw/12345/myfolder/file2.yml",
+ blob: "http://localhost/#{project.full_path}/-/blob/#{sha}/myfolder/file2.yml",
+ raw: "http://localhost/#{project.full_path}/-/raw/#{sha}/myfolder/file2.yml",
location: 'myfolder/file2.yml',
extra: {},
context_project: project.full_path,
- context_sha: '12345' }
+ context_sha: sha }
)
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index c4a6641ff6b..b48a89059bf 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config do
+RSpec.describe Gitlab::Ci::Config, feature_category: :pipeline_authoring do
include StubRequests
let_it_be(:user) { create(:user) }
@@ -305,7 +305,7 @@ RSpec.describe Gitlab::Ci::Config do
it 'raises error' do
expect { config }.to raise_error(
described_class::ConfigError,
- /\!reference \["job-2", "before_script"\] is part of a circular chain/
+ /!reference \["job-2", "before_script"\] is part of a circular chain/
)
end
end
@@ -503,7 +503,7 @@ RSpec.describe Gitlab::Ci::Config do
expect { config }.to raise_error(
described_class::ConfigError,
- 'Resolving config took longer than expected'
+ 'Request timed out when fetching configuration files.'
)
end
end
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 33474865a93..4b750cf3bcf 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -358,4 +358,22 @@ RSpec.describe Gitlab::Ci::CronParser do
end
end
end
+
+ describe '#match?' do
+ let(:run_date) { Time.zone.local(2021, 3, 2, 1, 0) }
+
+ subject(:matched) { described_class.new(cron, Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE).match?(run_date) }
+
+ context 'when cron matches up' do
+ let(:cron) { '0 1 2 3 *' }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when cron does not match' do
+ let(:cron) { '5 4 3 2 1' }
+
+ it { is_expected.to eq(false) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/environment_matcher_spec.rb b/spec/lib/gitlab/ci/environment_matcher_spec.rb
new file mode 100644
index 00000000000..172ada1b764
--- /dev/null
+++ b/spec/lib/gitlab/ci/environment_matcher_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::EnvironmentMatcher, feature_category: :continuous_integration do
+ describe '#match?' do
+ context 'when given pattern is a normal string' do
+ subject { described_class.new('production') }
+
+ it 'returns true on an exact match' do
+ expect(subject.match?('production')).to eq true
+ end
+
+ it 'returns false if not an exact match' do
+ expect(subject.match?('productiom')).to eq false
+ end
+ end
+
+ context 'when given pattern has a wildcard' do
+ it 'returns true on wildcard matches', :aggregate_failures do
+ expect(described_class.new('review/*').match?('review/123')).to eq true
+ expect(described_class.new('review/*/*').match?('review/123/456')).to eq true
+ expect(described_class.new('*-this-is-a-pattern-*').match?('abc123-this-is-a-pattern-abc123')).to eq true
+ end
+
+ it 'returns false when not a wildcard match', :aggregate_failures do
+ expect(described_class.new('review/*').match?('review123')).to eq false
+ expect(described_class.new('review/*/*').match?('review/123')).to eq false
+ expect(described_class.new('*-this-is-a-pattern-*').match?('abc123-this-is-a-pattern')).to eq false
+ end
+ end
+
+ context 'when given pattern is nil' do
+ subject { described_class.new(nil) }
+
+ it 'always returns false' do
+ expect(subject.match?('production')).to eq false
+ expect(subject.match?('review/123')).to eq false
+ end
+ end
+
+ context 'when given pattern is an empty string' do
+ subject { described_class.new('') }
+
+ it 'always returns false' do
+ expect(subject.match?('production')).to eq false
+ expect(subject.match?('review/123')).to eq false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb
index cf07e952f26..b836ca395fa 100644
--- a/spec/lib/gitlab/ci/lint_spec.rb
+++ b/spec/lib/gitlab/ci/lint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Lint do
+RSpec.describe Gitlab::Ci::Lint, feature_category: :pipeline_authoring do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
@@ -337,35 +337,28 @@ RSpec.describe Gitlab::Ci::Lint do
end
end
- context 'pipeline logger' do
- let(:counters) do
- {
- 'count' => a_kind_of(Numeric),
- 'avg' => a_kind_of(Numeric),
- 'sum' => a_kind_of(Numeric),
- 'max' => a_kind_of(Numeric),
- 'min' => a_kind_of(Numeric)
- }
- end
-
- let(:loggable_data) do
+ describe 'pipeline logger' do
+ let(:expected_data) do
{
'class' => 'Gitlab::Ci::Pipeline::Logger',
- 'config_build_context_duration_s' => counters,
- 'config_build_variables_duration_s' => counters,
- 'config_compose_duration_s' => counters,
- 'config_expand_duration_s' => counters,
- 'config_external_process_duration_s' => counters,
- 'config_stages_inject_duration_s' => counters,
- 'config_tags_resolve_duration_s' => counters,
- 'config_yaml_extend_duration_s' => counters,
- 'config_yaml_load_duration_s' => counters,
+ 'config_build_context_duration_s' => a_kind_of(Numeric),
+ 'config_build_variables_duration_s' => a_kind_of(Numeric),
+ 'config_root_duration_s' => a_kind_of(Numeric),
+ 'config_root_compose_duration_s' => a_kind_of(Numeric),
+ 'config_root_compose_jobs_factory_duration_s' => a_kind_of(Numeric),
+ 'config_root_compose_jobs_create_duration_s' => a_kind_of(Numeric),
+ 'config_expand_duration_s' => a_kind_of(Numeric),
+ 'config_external_process_duration_s' => a_kind_of(Numeric),
+ 'config_stages_inject_duration_s' => a_kind_of(Numeric),
+ 'config_tags_resolve_duration_s' => a_kind_of(Numeric),
+ 'config_yaml_extend_duration_s' => a_kind_of(Numeric),
+ 'config_yaml_load_duration_s' => a_kind_of(Numeric),
'pipeline_creation_caller' => 'Gitlab::Ci::Lint',
'pipeline_creation_service_duration_s' => a_kind_of(Numeric),
'pipeline_persisted' => false,
'pipeline_source' => 'unknown',
'project_id' => project&.id,
- 'yaml_process_duration_s' => counters
+ 'yaml_process_duration_s' => a_kind_of(Numeric)
}
end
@@ -403,7 +396,7 @@ RSpec.describe Gitlab::Ci::Lint do
end
it 'creates a log entry' do
- expect(Gitlab::AppJsonLogger).to receive(:info).with(loggable_data)
+ expect(Gitlab::AppJsonLogger).to receive(:info).with(a_hash_including(expected_data))
validate
end
@@ -424,11 +417,11 @@ RSpec.describe Gitlab::Ci::Lint do
let(:project) { nil }
let(:project_nil_loggable_data) do
- loggable_data.except('project_id')
+ expected_data.except('project_id')
end
it 'creates a log entry without project_id' do
- expect(Gitlab::AppJsonLogger).to receive(:info).with(project_nil_loggable_data)
+ expect(Gitlab::AppJsonLogger).to receive(:info).with(a_hash_including(project_nil_loggable_data))
validate
end
diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb
index f09b85aa2c7..dacbe07c8b3 100644
--- a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties do
+RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties, feature_category: :dependency_management do
subject(:parse_source_from_properties) { described_class.parse_source(properties) }
context 'when properties are nil' do
diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb
index 0b094880f69..d06537ac330 100644
--- a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx do
+RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx, feature_category: :dependency_management do
let(:report) { instance_double('Gitlab::Ci::Reports::Sbom::Report') }
let(:report_data) { base_report_data }
let(:raw_report_data) { report_data.to_json }
diff --git a/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb
index e12fa380209..bc97eb2d950 100644
--- a/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning do
+RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning, feature_category: :dependency_management do
subject { described_class.source(property_data) }
context 'when all property data is present' do
diff --git a/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb
index f58a463f047..712dc00ec7a 100644
--- a/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb
@@ -2,7 +2,8 @@
require "spec_helper"
-RSpec.describe Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator do
+RSpec.describe Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator,
+ feature_category: :dependency_management do
# Reports should be valid or invalid according to the specification at
# https://cyclonedx.org/docs/1.4/json/
diff --git a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb
index e730afc72b5..c94ed1f8d6d 100644
--- a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb
@@ -95,7 +95,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
context 'when all files under schema path are explicitly listed' do
# We only care about the part that comes before report-format.json
# https://rubular.com/r/N8Juz7r8hYDYgD
- filename_regex = /(?<report_type>[-\w]*)\-report-format.json/
+ filename_regex = /(?<report_type>[-\w]*)-report-format.json/
versions = Dir.glob(File.join(schema_path, "*", File::SEPARATOR)).map { |path| path.split("/").last }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
index 15df5b2f68c..74a68f28f3e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
@@ -10,13 +10,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::AssignPartition do
Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
end
- let(:pipeline) { build(:ci_pipeline, project: project) }
+ let(:pipeline) { build(:ci_pipeline, project: project, partition_id: nil) }
let(:step) { described_class.new(pipeline, command) }
let(:current_partition_id) { 123 }
describe '#perform!' do
+ include Ci::PartitioningHelpers
+
before do
- allow(Ci::Pipeline).to receive(:current_partition_value) { current_partition_id }
+ stub_current_partition_id(current_partition_id)
end
subject { step.perform! }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
index 32c92724f62..b2128f77960 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
@@ -2,11 +2,12 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Build::Associations, feature_category: :continuous_integration do
let_it_be_with_reload(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
- let(:pipeline) { Ci::Pipeline.new }
+ # Assigning partition_id here to validate it is being propagated correctly
+ let(:pipeline) { Ci::Pipeline.new(partition_id: ci_testing_partition_id) }
let(:bridge) { nil }
let(:variables_attributes) do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb
index fc3de2a14cd..16deeb6916f 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb
@@ -173,21 +173,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines do
expect(build_statuses(prev_pipeline)).to contain_exactly('running', 'success', 'created')
expect(build_statuses(parent_pipeline)).to contain_exactly('running', 'running')
end
-
- context 'when feature flag ci_skip_auto_cancelation_on_child_pipelines is disabled' do
- before do
- stub_feature_flags(ci_skip_auto_cancelation_on_child_pipelines: false)
- end
-
- it 'does not cancel the parent pipeline' do
- expect(build_statuses(parent_pipeline)).to contain_exactly('running', 'running')
-
- perform
-
- expect(build_statuses(prev_pipeline)).to contain_exactly('success', 'canceled', 'canceled')
- expect(build_statuses(parent_pipeline)).to contain_exactly('running', 'running')
- end
- end
end
context 'when the previous pipeline source is webide' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 9126c6dab21..68158503628 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -374,21 +374,57 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
end
end
+ describe '#observe_creation_duration' do
+ let(:histogram) { instance_double(Prometheus::Client::Histogram) }
+ let(:duration) { 1.hour }
+ let(:command) { described_class.new(project: project) }
+
+ subject(:observe_creation_duration) do
+ command.observe_creation_duration(duration)
+ end
+
+ it 'records the duration as histogram' do
+ expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_creation_duration_histogram)
+ .and_return(histogram)
+ expect(histogram).to receive(:observe)
+ .with({ gitlab: 'false' }, duration.seconds)
+
+ observe_creation_duration
+ end
+
+ context 'when project is gitlab-org/gitlab' do
+ before do
+ allow(project).to receive(:full_path).and_return('gitlab-org/gitlab')
+ end
+
+ it 'tracks the duration with the expected label' do
+ expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_creation_duration_histogram)
+ .and_return(histogram)
+ expect(histogram).to receive(:observe)
+ .with({ gitlab: 'true' }, duration.seconds)
+
+ observe_creation_duration
+ end
+ end
+ end
+
describe '#observe_step_duration' do
+ let(:histogram) { instance_double(Prometheus::Client::Histogram) }
+ let(:duration) { 1.hour }
+ let(:command) { described_class.new }
+
+ subject(:observe_step_duration) do
+ command.observe_step_duration(Gitlab::Ci::Pipeline::Chain::Build, duration)
+ end
+
context 'when ci_pipeline_creation_step_duration_tracking is enabled' do
it 'adds the duration to the step duration histogram' do
- histogram = instance_double(Prometheus::Client::Histogram)
- duration = 1.hour
-
expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_creation_step_duration_histogram)
.and_return(histogram)
expect(histogram).to receive(:observe)
.with({ step: 'Gitlab::Ci::Pipeline::Chain::Build' }, duration.seconds)
- described_class.new.observe_step_duration(
- Gitlab::Ci::Pipeline::Chain::Build,
- duration
- )
+ observe_step_duration
end
end
@@ -398,14 +434,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
end
it 'does nothing' do
- duration = 1.hour
-
expect(::Gitlab::Ci::Pipeline::Metrics).not_to receive(:pipeline_creation_step_duration_histogram)
- described_class.new.observe_step_duration(
- Gitlab::Ci::Pipeline::Chain::Build,
- duration
- )
+ observe_step_duration
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb
index 7fb5b0b4200..39520149032 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb
@@ -36,9 +36,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments, :aggregate_failu
end
context 'and the pipeline is for a merge request' do
- let(:command) do
- Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user, merge_request: merge_request)
- end
+ let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage], merge_request: merge_request) }
it 'associates the environment with the merge request' do
expect { subject }.to change { Environment.count }.by(1)
@@ -62,9 +60,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments, :aggregate_failu
end
context 'and the pipeline is for a merge request' do
- let(:command) do
- Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user, merge_request: merge_request)
- end
+ let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage], merge_request: merge_request) }
it 'does not associate the environment with the merge request' do
expect { subject }.not_to change { Environment.count }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 7aaeee32f49..9373888aada 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -2,8 +2,8 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
- let_it_be(:project, reload: true) { create(:project, :repository) }
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities, feature_category: :pipeline_execution do
+ let(:project) { create(:project, :test_repo) }
let_it_be(:user) { create(:user) }
let(:pipeline) do
diff --git a/spec/lib/gitlab/ci/pipeline/logger_spec.rb b/spec/lib/gitlab/ci/pipeline/logger_spec.rb
index 3af0ebe7484..1c285889d1b 100644
--- a/spec/lib/gitlab/ci/pipeline/logger_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/logger_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
+RSpec.describe ::Gitlab::Ci::Pipeline::Logger, feature_category: :continuous_integration do
let_it_be(:project) { build_stubbed(:project) }
let_it_be(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
@@ -22,61 +22,54 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
end
it 'records durations of instrumented operations' do
- loggable_data = {
+ logger.instrument(:expensive_operation) { 123 }
+
+ expected_data = {
'expensive_operation_duration_s' => {
'count' => 1,
- 'sum' => a_kind_of(Numeric),
- 'avg' => a_kind_of(Numeric),
'max' => a_kind_of(Numeric),
- 'min' => a_kind_of(Numeric)
+ 'sum' => a_kind_of(Numeric)
}
}
-
- logger.instrument(:expensive_operation) { 123 }
- expect(logger.observations_hash).to match(a_hash_including(loggable_data))
+ expect(logger.observations_hash).to match(a_hash_including(expected_data))
end
it 'raises an error when block is not provided' do
expect { logger.instrument(:expensive_operation) }
.to raise_error(ArgumentError, 'block not given')
end
+
+ context 'when once: true' do
+ it 'logs only one observation' do
+ logger.instrument(:expensive_operation, once: true) { 123 }
+ logger.instrument(:expensive_operation, once: true) { 123 }
+
+ expected_data = {
+ 'expensive_operation_duration_s' => a_kind_of(Numeric)
+ }
+ expect(logger.observations_hash).to match(a_hash_including(expected_data))
+ end
+ end
end
- describe '#instrument_with_sql', :request_store do
- subject(:instrument_with_sql) do
- logger.instrument_with_sql(:expensive_operation, &operation)
+ describe '#instrument_once_with_sql', :request_store do
+ subject(:instrument_once_with_sql) do
+ logger.instrument_once_with_sql(:expensive_operation, &operation)
end
- def loggable_data(count:, db_count: nil)
+ def expected_data(count:, db_count: nil)
database_name = Ci::ApplicationRecord.connection.pool.db_config.name
- keys = %W[
- expensive_operation_duration_s
- expensive_operation_db_count
- expensive_operation_db_primary_count
- expensive_operation_db_primary_duration_s
- expensive_operation_db_#{database_name}_count
- expensive_operation_db_#{database_name}_duration_s
- ]
-
- data = keys.each.with_object({}) do |key, accumulator|
- accumulator[key] = {
- 'count' => count,
- 'avg' => a_kind_of(Numeric),
- 'sum' => a_kind_of(Numeric),
- 'max' => a_kind_of(Numeric),
- 'min' => a_kind_of(Numeric)
- }
- end
-
- if db_count
- data['expensive_operation_db_count']['max'] = db_count
- data['expensive_operation_db_count']['min'] = db_count
- data['expensive_operation_db_count']['avg'] = db_count
- data['expensive_operation_db_count']['sum'] = count * db_count
- end
+ total_db_count = count * db_count if db_count
- data
+ {
+ "expensive_operation_duration_s" => a_kind_of(Numeric),
+ "expensive_operation_db_count" => total_db_count || a_kind_of(Numeric),
+ "expensive_operation_db_primary_count" => a_kind_of(Numeric),
+ "expensive_operation_db_primary_duration_s" => a_kind_of(Numeric),
+ "expensive_operation_db_#{database_name}_count" => a_kind_of(Numeric),
+ "expensive_operation_db_#{database_name}_duration_s" => a_kind_of(Numeric)
+ }
end
context 'with a single query' do
@@ -85,10 +78,10 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
it { is_expected.to eq(operation.call) }
it 'includes SQL metrics' do
- instrument_with_sql
+ instrument_once_with_sql
expect(logger.observations_hash)
- .to match(a_hash_including(loggable_data(count: 1, db_count: 1)))
+ .to match(a_hash_including(expected_data(count: 1, db_count: 1)))
end
end
@@ -98,21 +91,10 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
it { is_expected.to eq(operation.call) }
it 'includes SQL metrics' do
- instrument_with_sql
-
- expect(logger.observations_hash)
- .to match(a_hash_including(loggable_data(count: 1, db_count: 2)))
- end
- end
-
- context 'with multiple observations' do
- let(:operation) { -> { Ci::Build.count + Ci::Bridge.count } }
-
- it 'includes SQL metrics' do
- 2.times { logger.instrument_with_sql(:expensive_operation, &operation) }
+ instrument_once_with_sql
expect(logger.observations_hash)
- .to match(a_hash_including(loggable_data(count: 2, db_count: 2)))
+ .to match(a_hash_including(expected_data(count: 1, db_count: 2)))
end
end
@@ -122,7 +104,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
it { is_expected.to eq(operation.call) }
it 'does not include SQL metrics' do
- instrument_with_sql
+ instrument_once_with_sql
expect(logger.observations_hash.keys)
.to match_array(['expensive_operation_duration_s'])
@@ -132,14 +114,40 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
describe '#observe' do
it 'records durations of observed operations' do
- loggable_data = {
+ expect(logger.observe(:pipeline_creation_duration_s, 30)).to be_truthy
+
+ expected_data = {
'pipeline_creation_duration_s' => {
- 'avg' => 30, 'sum' => 30, 'count' => 1, 'max' => 30, 'min' => 30
+ 'sum' => 30, 'count' => 1, 'max' => 30
}
}
+ expect(logger.observations_hash).to match(a_hash_including(expected_data))
+ end
- expect(logger.observe(:pipeline_creation_duration_s, 30)).to be_truthy
- expect(logger.observations_hash).to match(a_hash_including(loggable_data))
+ context 'when once: true' do
+ it 'records the latest observation' do
+ expect(logger.observe(:pipeline_creation_duration_s, 20, once: true)).to be_truthy
+ expect(logger.observe(:pipeline_creation_duration_s, 30, once: true)).to be_truthy
+
+ expected_data = {
+ 'pipeline_creation_duration_s' => 30
+ }
+ expect(logger.observations_hash).to match(a_hash_including(expected_data))
+ end
+
+ it 'logs data as expected' do
+ expect(logger.observe(:pipeline_creation_duration_s, 30, once: true)).to be_truthy
+ expect(logger.observe(:pipeline_operation_x_duration_s, 20)).to be_truthy
+ expect(logger.observe(:pipeline_operation_x_duration_s, 20)).to be_truthy
+
+ expected_data = {
+ 'pipeline_creation_duration_s' => 30,
+ 'pipeline_operation_x_duration_s' => {
+ 'sum' => 40, 'count' => 2, 'max' => 20
+ }
+ }
+ expect(logger.observations_hash).to match(a_hash_including(expected_data))
+ end
end
end
@@ -158,8 +166,11 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
context 'when the feature flag is enabled' do
let(:flag) { true }
- let(:loggable_data) do
+ let(:expected_data) do
{
+ 'correlation_id' => a_kind_of(String),
+ 'meta.project' => project.full_path,
+ 'meta.root_namespace' => project.root_namespace.full_path,
'class' => described_class.name.to_s,
'pipeline_id' => pipeline.id,
'pipeline_persisted' => true,
@@ -168,10 +179,10 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
'pipeline_creation_caller' => 'source',
'pipeline_source' => pipeline.source,
'pipeline_save_duration_s' => {
- 'avg' => 60, 'sum' => 60, 'count' => 1, 'max' => 60, 'min' => 60
+ 'sum' => 60, 'count' => 1, 'max' => 60
},
'pipeline_creation_duration_s' => {
- 'avg' => 20, 'sum' => 40, 'count' => 2, 'max' => 30, 'min' => 10
+ 'sum' => 40, 'count' => 2, 'max' => 30
}
}
end
@@ -179,7 +190,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
it 'logs to application.json' do
expect(Gitlab::AppJsonLogger)
.to receive(:info)
- .with(a_hash_including(loggable_data))
+ .with(a_hash_including(expected_data))
.and_call_original
expect(commit).to be_truthy
@@ -200,28 +211,43 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
expect(Gitlab::AppJsonLogger)
.to receive(:info)
- .with(a_hash_including(loggable_data))
+ .with(a_hash_including(expected_data))
.and_call_original
expect(commit).to be_truthy
end
+
+ context 'with unexistent observations in condition' do
+ it 'does not commit the log' do
+ logger.log_when do |observations|
+ value = observations['non_existent_value']
+ next false unless value
+
+ value > 0
+ end
+
+ expect(Gitlab::AppJsonLogger).not_to receive(:info)
+
+ expect(commit).to be_falsey
+ end
+ end
end
context 'when project is not passed and pipeline is not persisted' do
let(:project) {}
let(:pipeline) { build(:ci_pipeline) }
- let(:loggable_data) do
+ let(:expected_data) do
{
'class' => described_class.name.to_s,
'pipeline_persisted' => false,
'pipeline_creation_service_duration_s' => a_kind_of(Numeric),
'pipeline_creation_caller' => 'source',
'pipeline_save_duration_s' => {
- 'avg' => 60, 'sum' => 60, 'count' => 1, 'max' => 60, 'min' => 60
+ 'sum' => 60, 'count' => 1, 'max' => 60
},
'pipeline_creation_duration_s' => {
- 'avg' => 20, 'sum' => 40, 'count' => 2, 'max' => 30, 'min' => 10
+ 'sum' => 40, 'count' => 2, 'max' => 30
}
}
end
@@ -229,7 +255,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
it 'logs to application.json' do
expect(Gitlab::AppJsonLogger)
.to receive(:info)
- .with(a_hash_including(loggable_data))
+ .with(a_hash_including(expected_data))
.and_call_original
expect(commit).to be_truthy
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 910c12389c3..fb8020bf43e 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb
@@ -6,8 +6,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:head_sha) { project.repository.head_commit.id }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: head_sha) }
+ let(:index) { 1 }
- let(:processor) { described_class.new(pipeline, config) }
+ let(:processor) { described_class.new(pipeline, config, index) }
describe '#attributes' do
subject { processor.attributes }
@@ -40,10 +41,12 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
{ key: { files: files } }
end
- it 'uses default key' do
- expected = { key: 'default' }
+ context 'without a prefix' do
+ it 'uses default key with an index as a prefix' do
+ expected = { key: '1-default' }
- is_expected.to include(expected)
+ is_expected.to include(expected)
+ end
end
end
@@ -57,13 +60,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
}
end
- it 'builds a string key' do
- expected = {
- key: '703ecc8fef1635427a1f86a8a1a308831c122392',
- paths: ['vendor/ruby']
- }
+ context 'without a prefix' do
+ it 'builds a string key with an index as a prefix' do
+ expected = {
+ key: '1-703ecc8fef1635427a1f86a8a1a308831c122392',
+ paths: ['vendor/ruby']
+ }
- is_expected.to include(expected)
+ is_expected.to include(expected)
+ end
end
end
@@ -107,10 +112,12 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do
}
end
- it 'builds a string key' do
- expected = { key: '74bf43fb1090f161bdd4e265802775dbda2f03d1' }
+ context 'without a prefix' do
+ it 'builds a string key with an index as a prefix' do
+ expected = { key: '1-74bf43fb1090f161bdd4e265802775dbda2f03d1' }
- is_expected.to include(expected)
+ is_expected.to include(expected)
+ 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 75f6a773c2d..1f7f800e238 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_authoring do
let_it_be_with_reload(:project) { create(:project, :repository) }
let_it_be(:head_sha) { project.repository.head_commit.id }
@@ -11,861 +11,954 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let(:seed_context) { Gitlab::Ci::Pipeline::Seed::Context.new(pipeline, root_variables: root_variables) }
let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage, when: 'on_success' } }
let(:previous_stages) { [] }
- let(:current_stage) { double(seeds_names: [attributes[:name]]) }
+ let(:current_stage) { instance_double(Gitlab::Ci::Pipeline::Seed::Stage, seeds_names: [attributes[:name]]) }
+ let(:current_ci_stage) { build(:ci_stage, pipeline: pipeline) }
- let(:seed_build) { described_class.new(seed_context, attributes, previous_stages + [current_stage]) }
+ let(:seed_build) { described_class.new(seed_context, attributes, previous_stages + [current_stage], current_ci_stage) }
- describe '#attributes' do
- subject { seed_build.attributes }
+ shared_examples 'build seed' do
+ describe '#attributes' do
+ subject { seed_build.attributes }
- it { is_expected.to be_a(Hash) }
- it { is_expected.to include(:name, :project, :ref) }
+ it { is_expected.to be_a(Hash) }
+ it { is_expected.to include(:name, :project, :ref) }
- context 'with job:when' do
- let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } }
+ context 'with job:when' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } }
- it { is_expected.to include(when: 'on_failure') }
- end
-
- context 'with job:when:delayed' do
- let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', start_in: '3 hours' } }
-
- it { is_expected.to include(when: 'delayed', start_in: '3 hours') }
- end
-
- context 'with job:rules:[when:]' do
- context 'is matched' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } }
-
- it { is_expected.to include(when: 'always') }
+ it { is_expected.to include(when: 'on_failure') }
end
- context 'is not matched' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } }
-
- it { is_expected.to include(when: 'never') }
- end
- end
-
- context 'with job:rules:[when:delayed]' do
- context 'is matched' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } }
+ context 'with job:when:delayed' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', options: { start_in: '3 hours' } } }
it { is_expected.to include(when: 'delayed', options: { start_in: '3 hours' }) }
end
- context 'is not matched' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } }
-
- it { is_expected.to include(when: 'never') }
- end
- end
-
- context 'with job: rules but no explicit when:' do
- let(:base_attributes) { { name: 'rspec', ref: 'master' } }
-
- context 'with a manual job' do
- context 'with a matched rule' do
- let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR == null' }]) }
+ context 'with job:rules:[when:]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } }
- it { is_expected.to include(when: 'manual') }
+ it { is_expected.to include(when: 'always') }
end
context 'is not matched' do
- let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR != null' }]) }
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } }
it { is_expected.to include(when: 'never') }
end
end
- context 'with an automatic job' do
+ context 'with job:rules:[when:delayed]' do
context 'is matched' do
- let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR == null' }]) }
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } }
- it { is_expected.to include(when: 'on_success') }
+ it { is_expected.to include(when: 'delayed', options: { start_in: '3 hours' }) }
end
context 'is not matched' do
- let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR != null' }]) }
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } }
it { is_expected.to include(when: 'never') }
end
end
- end
- context 'with job:rules:[variables:]' do
- let(:attributes) do
- { name: 'rspec',
- ref: 'master',
- job_variables: [{ key: 'VAR1', value: 'var 1' },
- { key: 'VAR2', value: 'var 2' }],
- rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
- end
+ context 'with job: rules but no explicit when:' do
+ let(:base_attributes) { { name: 'rspec', ref: 'master' } }
- it do
- is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' },
- { key: 'VAR3', value: 'var 3' },
- { key: 'VAR2', value: 'var 2' }])
- end
- end
+ context 'with a manual job' do
+ context 'with a matched rule' do
+ let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR == null' }]) }
- context 'with job:tags' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- job_variables: [{ key: 'VARIABLE', value: 'value' }],
- tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE']
- }
- end
+ it { is_expected.to include(when: 'manual') }
+ end
- it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) }
- it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) }
- end
+ context 'is not matched' do
+ let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR != null' }]) }
- context 'with cache:key' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: [{
- key: 'a-value'
- }]
- }
- end
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with an automatic job' do
+ context 'is matched' do
+ let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR == null' }]) }
+
+ it { is_expected.to include(when: 'on_success') }
+ end
- it { is_expected.to include(options: { cache: [a_hash_including(key: 'a-value')] }) }
+ context 'is not matched' do
+ let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR != null' }]) }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+ end
- context 'with cache:key:files' do
+ context 'with job:rules:[variables:]' do
let(:attributes) do
- {
- name: 'rspec',
+ { name: 'rspec',
ref: 'master',
- cache: [{
- key: {
- files: ['VERSION']
- }
- }]
- }
+ job_variables: [{ key: 'VAR1', value: 'var 1' },
+ { key: 'VAR2', value: 'var 2' }],
+ rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
end
- it 'includes cache options' do
- cache_options = {
- options: {
- cache: [a_hash_including(key: 'f155568ad0933d8358f66b846133614f76dd0ca4')]
- }
- }
+ it do
+ is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' },
+ { key: 'VAR3', value: 'var 3' },
+ { key: 'VAR2', value: 'var 2' }])
+ end
- is_expected.to include(cache_options)
+ it 'expects the same results on to_resource' do
+ expect(seed_build.to_resource.yaml_variables).to include({ key: 'VAR1', value: 'new var 1' },
+ { key: 'VAR3', value: 'var 3' },
+ { key: 'VAR2', value: 'var 2' })
end
end
- context 'with cache:key:prefix' do
+ context 'with job:tags' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
- cache: [{
- key: {
- prefix: 'something'
- }
- }]
+ job_variables: [{ key: 'VARIABLE', value: 'value' }],
+ tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE']
}
end
- it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) }
+ it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) }
+ it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) }
end
- context 'with cache:key:files and prefix' do
+ context 'with cache:key' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: [{
- key: {
- files: ['VERSION'],
- prefix: 'something'
- }
+ key: 'a-value'
}]
}
end
- it 'includes cache options' do
- cache_options = {
- options: {
- cache: [a_hash_including(key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4')]
+ it { is_expected.to include(options: { cache: [a_hash_including(key: 'a-value')] }) }
+
+ context 'with cache:key:files' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: [{
+ key: {
+ files: ['VERSION']
+ }
+ }]
}
- }
+ end
- is_expected.to include(cache_options)
- end
- end
- end
+ it 'includes cache options' do
+ cache_options = {
+ options: {
+ cache: [a_hash_including(key: '0-f155568ad0933d8358f66b846133614f76dd0ca4')]
+ }
+ }
- context 'with empty cache' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: {}
- }
- end
+ is_expected.to include(cache_options)
+ end
+ end
- it { is_expected.to include({}) }
- end
+ context 'with cache:key:prefix' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: [{
+ key: {
+ prefix: 'something'
+ }
+ }]
+ }
+ end
- context 'with allow_failure' do
- let(:options) do
- { allow_failure_criteria: { exit_codes: [42] } }
- end
+ it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) }
+ end
- let(:rules) do
- [{ if: '$VAR == null', when: 'always' }]
- end
+ context 'with cache:key:files and prefix' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: [{
+ key: {
+ files: ['VERSION'],
+ prefix: 'something'
+ }
+ }]
+ }
+ end
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- options: options,
- rules: rules
- }
- end
+ it 'includes cache options' do
+ cache_options = {
+ options: {
+ cache: [a_hash_including(key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4')]
+ }
+ }
- context 'when rules does not override allow_failure' do
- it { is_expected.to match a_hash_including(options: options) }
+ is_expected.to include(cache_options)
+ end
+ end
end
- context 'when rules set allow_failure to true' do
- let(:rules) do
- [{ if: '$VAR == null', when: 'always', allow_failure: true }]
+ context 'with empty cache' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: {}
+ }
end
- it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
+ it { is_expected.to include({}) }
end
- context 'when rules set allow_failure to false' do
- let(:rules) do
- [{ if: '$VAR == null', when: 'always', allow_failure: false }]
+ context 'with allow_failure' do
+ let(:options) do
+ { allow_failure_criteria: { exit_codes: [42] } }
end
- it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
- end
- end
-
- context 'with workflow:rules:[variables:]' do
- let(:attributes) do
- { name: 'rspec',
- ref: 'master',
- yaml_variables: [{ key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }],
- job_variables: [{ key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }],
- root_variables_inheritance: root_variables_inheritance }
- end
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always' }]
+ end
- context 'when the pipeline has variables' do
- let(:root_variables) do
- [{ key: 'VAR1', value: 'var overridden pipeline 1' },
- { key: 'VAR2', value: 'var pipeline 2' },
- { key: 'VAR3', value: 'var pipeline 3' },
- { key: 'VAR4', value: 'new var pipeline 4' }]
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ options: options,
+ rules: rules
+ }
end
- context 'when root_variables_inheritance is true' do
- let(:root_variables_inheritance) { true }
+ context 'when rules does not override allow_failure' do
+ it { is_expected.to match a_hash_including(options: options) }
+ end
- it 'returns calculated yaml variables' do
- expect(subject[:yaml_variables]).to match_array(
- [{ key: 'VAR1', value: 'var overridden pipeline 1' },
- { key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' },
- { key: 'VAR4', value: 'new var pipeline 4' }]
- )
+ context 'when rules set allow_failure to true' do
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always', allow_failure: true }]
end
- end
- context 'when root_variables_inheritance is false' do
- let(:root_variables_inheritance) { false }
+ it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
- it 'returns job variables' do
- expect(subject[:yaml_variables]).to match_array(
- [{ key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }]
- )
- end
- end
+ context 'when options contain other static values' do
+ let(:options) do
+ { image: 'busybox', allow_failure_criteria: { exit_codes: [42] } }
+ end
- context 'when root_variables_inheritance is an array' do
- let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) }
+ it { is_expected.to match a_hash_including(options: { image: 'busybox', allow_failure_criteria: nil }) }
- it 'returns calculated yaml variables' do
- expect(subject[:yaml_variables]).to match_array(
- [{ key: 'VAR1', value: 'var overridden pipeline 1' },
- { key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }]
- )
+ it 'deep merges options when exporting to_resource' do
+ expect(seed_build.to_resource.options).to match a_hash_including(
+ image: 'busybox', allow_failure_criteria: nil
+ )
+ end
end
end
- end
- context 'when the pipeline has not a variable' do
- let(:root_variables_inheritance) { true }
+ context 'when rules set allow_failure to false' do
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always', allow_failure: false }]
+ end
- it 'returns seed yaml variables' do
- expect(subject[:yaml_variables]).to match_array(
- [{ key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }])
+ it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
end
end
- end
- context 'when the job rule depends on variables' do
- let(:attributes) do
- { name: 'rspec',
- ref: 'master',
- yaml_variables: [{ key: 'VAR1', value: 'var 1' }],
- job_variables: [{ key: 'VAR1', value: 'var 1' }],
- root_variables_inheritance: root_variables_inheritance,
- rules: rules }
- end
+ context 'with workflow:rules:[variables:]' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }],
+ job_variables: [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }],
+ root_variables_inheritance: root_variables_inheritance }
+ end
+
+ context 'when the pipeline has variables' do
+ let(:root_variables) do
+ [{ key: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var pipeline 2' },
+ { key: 'VAR3', value: 'var pipeline 3' },
+ { key: 'VAR4', value: 'new var pipeline 4' }]
+ end
- let(:root_variables_inheritance) { true }
+ context 'when root_variables_inheritance is true' do
+ let(:root_variables_inheritance) { true }
- context 'when the rules use job variables' do
- let(:rules) do
- [{ if: '$VAR1 == "var 1"', variables: { VAR1: 'overridden var 1', VAR2: 'new var 2' } }]
+ it 'returns calculated yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' },
+ { key: 'VAR4', value: 'new var pipeline 4' }]
+ )
+ end
+ end
+
+ context 'when root_variables_inheritance is false' do
+ let(:root_variables_inheritance) { false }
+
+ it 'returns job variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }]
+ )
+ end
+ end
+
+ context 'when root_variables_inheritance is an array' do
+ let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) }
+
+ it 'returns calculated yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }]
+ )
+ end
+ end
end
- it 'recalculates the variables' do
- expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
- { key: 'VAR2', value: 'new var 2' })
+ context 'when the pipeline has not a variable' do
+ let(:root_variables_inheritance) { true }
+
+ it 'returns seed yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }])
+ end
end
end
- context 'when the rules use root variables' do
- let(:root_variables) do
- [{ key: 'VAR2', value: 'var pipeline 2' }]
+ context 'when the job rule depends on variables' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR1', value: 'var 1' }],
+ job_variables: [{ key: 'VAR1', value: 'var 1' }],
+ root_variables_inheritance: root_variables_inheritance,
+ rules: rules }
end
- let(:rules) do
- [{ if: '$VAR2 == "var pipeline 2"', variables: { VAR1: 'overridden var 1', VAR2: 'overridden var 2' } }]
- end
+ let(:root_variables_inheritance) { true }
+
+ context 'when the rules use job variables' do
+ let(:rules) do
+ [{ if: '$VAR1 == "var 1"', variables: { VAR1: 'overridden var 1', VAR2: 'new var 2' } }]
+ end
- it 'recalculates the variables' do
- expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
- { key: 'VAR2', value: 'overridden var 2' })
+ it 'recalculates the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
+ { key: 'VAR2', value: 'new var 2' })
+ end
end
- context 'when the root_variables_inheritance is false' do
- let(:root_variables_inheritance) { false }
+ context 'when the rules use root variables' do
+ let(:root_variables) do
+ [{ key: 'VAR2', value: 'var pipeline 2' }]
+ end
- it 'does not recalculate the variables' do
- expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' })
+ let(:rules) do
+ [{ if: '$VAR2 == "var pipeline 2"', variables: { VAR1: 'overridden var 1', VAR2: 'overridden var 2' } }]
+ end
+
+ it 'recalculates the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
+ { key: 'VAR2', value: 'overridden var 2' })
end
- end
- end
- end
- end
- describe '#bridge?' do
- subject { seed_build.bridge? }
+ context 'when the root_variables_inheritance is false' do
+ let(:root_variables_inheritance) { false }
- context 'when job is a downstream bridge' do
- let(:attributes) do
- { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
+ it 'does not recalculate the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' })
+ end
+ end
+ end
end
+ end
- it { is_expected.to be_truthy }
+ describe '#bridge?' do
+ subject { seed_build.bridge? }
- context 'when trigger definition is empty' do
+ context 'when job is a downstream bridge' do
let(:attributes) do
- { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it { is_expected.to be_falsey }
- end
- end
+ it { is_expected.to be_truthy }
- context 'when job is an upstream bridge' do
- let(:attributes) do
- { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } }
- end
+ context 'when trigger definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ end
- it { is_expected.to be_truthy }
+ it { is_expected.to be_falsey }
+ end
+ end
- context 'when upstream definition is empty' do
+ context 'when job is an upstream bridge' do
let(:attributes) do
- { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } }
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } }
end
- it { is_expected.to be_falsey }
- end
- end
+ it { is_expected.to be_truthy }
- context 'when job is not a bridge' do
- it { is_expected.to be_falsey }
- end
- end
+ context 'when upstream definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } }
+ end
- describe '#to_resource' do
- subject { seed_build.to_resource }
+ it { is_expected.to be_falsey }
+ end
+ end
- it 'memoizes a resource object' do
- expect(subject.object_id).to eq seed_build.to_resource.object_id
+ context 'when job is not a bridge' do
+ it { is_expected.to be_falsey }
+ end
end
- it 'can not be persisted without explicit assignment' do
- pipeline.save!
+ describe '#to_resource' do
+ subject { seed_build.to_resource }
- expect(subject).not_to be_persisted
- end
- end
+ it 'memoizes a resource object' do
+ expect(subject.object_id).to eq seed_build.to_resource.object_id
+ end
- describe 'applying job inclusion policies' do
- subject { seed_build }
+ it 'can not be persisted without explicit assignment' do
+ pipeline.save!
- context 'when no branch policy is specified' do
- let(:attributes) do
- { name: 'rspec' }
+ expect(subject).not_to be_persisted
end
-
- it { is_expected.to be_included }
end
- context 'when branch policy does not match' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: ['deploy'] } }
- end
-
- it { is_expected.not_to be_included }
- end
+ describe 'applying job inclusion policies' do
+ subject { seed_build }
- context 'when using except' do
+ context 'when no branch policy is specified' do
let(:attributes) do
- { name: 'rspec', except: { refs: ['deploy'] } }
+ { name: 'rspec' }
end
it { is_expected.to be_included }
end
- context 'with both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[deploy] },
- except: { refs: %w[deploy] }
- }
+ context 'when branch policy does not match' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: ['deploy'] } }
+ end
+
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
- end
- end
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: ['deploy'] } }
+ end
- context 'when branch regexp policy does not match' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[/^deploy$/] } }
+ it { is_expected.to be_included }
end
- it { is_expected.not_to be_included }
- end
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy] },
+ except: { refs: %w[deploy] }
+ }
+ end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[/^deploy$/] } }
+ it { is_expected.not_to be_included }
end
-
- it { is_expected.to be_included }
end
- context 'with both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[/^deploy$/] },
- except: { refs: %w[/^deploy$/] }
- }
+ context 'when branch regexp policy does not match' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[/^deploy$/] } }
+ end
+
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
- end
- end
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[/^deploy$/] } }
+ end
- context 'when branch policy matches' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[deploy master] } }
+ it { is_expected.to be_included }
end
- it { is_expected.to be_included }
- end
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[/^deploy$/] },
+ except: { refs: %w[/^deploy$/] }
+ }
+ end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[deploy master] } }
+ it { is_expected.not_to be_included }
end
-
- it { is_expected.not_to be_included }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[deploy master] },
- except: { refs: %w[deploy master] }
- }
+ context 'when branch policy matches' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[deploy master] } }
+ end
+
+ it { is_expected.to be_included }
end
- it { is_expected.not_to be_included }
- end
- end
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[deploy master] } }
+ end
- context 'when keyword policy matches' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[branches] } }
+ it { is_expected.not_to be_included }
end
- it { is_expected.to be_included }
- end
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy master] },
+ except: { refs: %w[deploy master] }
+ }
+ end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[branches] } }
+ it { is_expected.not_to be_included }
end
-
- it { is_expected.not_to be_included }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[branches] },
- except: { refs: %w[branches] }
- }
+ context 'when keyword policy matches' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[branches] } }
+ end
+
+ it { is_expected.to be_included }
end
- it { is_expected.not_to be_included }
- end
- end
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[branches] } }
+ end
- context 'when keyword policy does not match' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[tags] } }
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
- end
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches] },
+ except: { refs: %w[branches] }
+ }
+ end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[tags] } }
+ it { is_expected.not_to be_included }
end
-
- it { is_expected.to be_included }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[tags] },
- except: { refs: %w[tags] }
- }
+ context 'when keyword policy does not match' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[tags] } }
+ end
+
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
- end
- end
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[tags] } }
+ end
- context 'with source-keyword policy' do
- using RSpec::Parameterized
+ it { is_expected.to be_included }
+ end
- let(:pipeline) do
- build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source, project: project)
- end
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[tags] },
+ except: { refs: %w[tags] }
+ }
+ end
- context 'matches' do
- where(:keyword, :source) do
- [
- %w[pushes push],
- %w[web web],
- %w[triggers trigger],
- %w[schedules schedule],
- %w[api api],
- %w[external external]
- ]
+ it { is_expected.not_to be_included }
end
+ end
- with_them do
- context 'using an only policy' do
- let(:attributes) do
- { name: 'rspec', only: { refs: [keyword] } }
- end
+ context 'with source-keyword policy' do
+ using RSpec::Parameterized
- it { is_expected.to be_included }
+ let(:pipeline) do
+ build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source, project: project)
+ end
+
+ context 'matches' do
+ where(:keyword, :source) do
+ [
+ %w[pushes push],
+ %w[web web],
+ %w[triggers trigger],
+ %w[schedules schedule],
+ %w[api api],
+ %w[external external]
+ ]
end
- context 'using an except policy' do
- let(:attributes) do
- { name: 'rspec', except: { refs: [keyword] } }
+ with_them do
+ context 'using an only policy' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
+
+ it { is_expected.to be_included }
end
- it { is_expected.not_to be_included }
- end
+ context 'using an except policy' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
- context 'using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: [keyword] },
- except: { refs: [keyword] }
- }
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
+ context 'using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
end
- end
- context 'non-matches' do
- where(:keyword, :source) do
- %w[web trigger schedule api external].map { |source| ['pushes', source] } +
- %w[push trigger schedule api external].map { |source| ['web', source] } +
- %w[push web schedule api external].map { |source| ['triggers', source] } +
- %w[push web trigger api external].map { |source| ['schedules', source] } +
- %w[push web trigger schedule external].map { |source| ['api', source] } +
- %w[push web trigger schedule api].map { |source| ['external', source] }
- end
+ context 'non-matches' do
+ where(:keyword, :source) do
+ %w[web trigger schedule api external].map { |source| ['pushes', source] } +
+ %w[push trigger schedule api external].map { |source| ['web', source] } +
+ %w[push web schedule api external].map { |source| ['triggers', source] } +
+ %w[push web trigger api external].map { |source| ['schedules', source] } +
+ %w[push web trigger schedule external].map { |source| ['api', source] } +
+ %w[push web trigger schedule api].map { |source| ['external', source] }
+ end
- with_them do
- context 'using an only policy' do
- let(:attributes) do
- { name: 'rspec', only: { refs: [keyword] } }
+ with_them do
+ context 'using an only policy' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
+
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
- end
+ context 'using an except policy' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
- context 'using an except policy' do
- let(:attributes) do
- { name: 'rspec', except: { refs: [keyword] } }
+ it { is_expected.to be_included }
end
- it { is_expected.to be_included }
- end
+ context 'using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
- context 'using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: [keyword] },
- except: { refs: [keyword] }
- }
+ it { is_expected.not_to be_included }
end
-
- it { is_expected.not_to be_included }
end
end
end
- end
- context 'when repository path matches' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: ["branches@#{pipeline.project_full_path}"] } }
+ context 'when repository path matches' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: ["branches@#{pipeline.project_full_path}"] } }
+ end
+
+ it { is_expected.to be_included }
end
- it { is_expected.to be_included }
- end
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: ["branches@#{pipeline.project_full_path}"] } }
+ end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: ["branches@#{pipeline.project_full_path}"] } }
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
- end
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: ["branches@#{pipeline.project_full_path}"] },
+ except: { refs: ["branches@#{pipeline.project_full_path}"] }
+ }
+ end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: ["branches@#{pipeline.project_full_path}"] },
- except: { refs: ["branches@#{pipeline.project_full_path}"] }
- }
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
- end
-
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: {
- refs: ["branches@#{pipeline.project_full_path}"]
- },
- except: {
- refs: ["branches@#{pipeline.project_full_path}"]
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: {
+ refs: ["branches@#{pipeline.project_full_path}"]
+ },
+ except: {
+ refs: ["branches@#{pipeline.project_full_path}"]
+ }
}
- }
- end
+ end
- it { is_expected.not_to be_included }
+ it { is_expected.not_to be_included }
+ end
end
- end
- context 'when repository path does not match' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[branches@fork] } }
+ context 'when repository path does not match' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[branches@fork] } }
+ end
+
+ it { is_expected.not_to be_included }
end
- it { is_expected.not_to be_included }
- end
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[branches@fork] } }
+ end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[branches@fork] } }
+ it { is_expected.to be_included }
end
- it { is_expected.to be_included }
- end
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches@fork] },
+ except: { refs: %w[branches@fork] }
+ }
+ end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[branches@fork] },
- except: { refs: %w[branches@fork] }
- }
+ it { is_expected.not_to be_included }
end
-
- it { is_expected.not_to be_included }
end
- end
- context 'using rules:' do
- using RSpec::Parameterized
+ context 'using rules:' do
+ using RSpec::Parameterized
- let(:attributes) { { name: 'rspec', rules: rule_set, when: 'on_success' } }
+ let(:attributes) { { name: 'rspec', rules: rule_set, when: 'on_success' } }
- context 'with a matching if: rule' do
- context 'with an explicit `when: never`' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE == null', when: 'never' }]],
- [[{ if: '$VARIABLE == null', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]],
- [[{ if: '$VARIABLE != "the wrong value"', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]]
- ]
- end
+ context 'with a matching if: rule' do
+ context 'with an explicit `when: never`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE == null', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]]
+ ]
+ end
- with_them do
- it { is_expected.not_to be_included }
+ with_them do
+ it { is_expected.not_to be_included }
- it 'still correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'never')
+ it 'still correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
end
end
- end
- context 'with an explicit `when: always`' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE == null', when: 'always' }]],
- [[{ if: '$VARIABLE == null', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]],
- [[{ if: '$VARIABLE != "the wrong value"', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]]
- ]
+ context 'with an explicit `when: always`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'always' }]],
+ [[{ if: '$VARIABLE == null', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'always')
+ end
+ end
end
- with_them do
- it { is_expected.to be_included }
+ context 'with an explicit `when: on_failure`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$CI_JOB_NAME == "rspec" && $VAR == null', when: 'on_failure' }]],
+ [[{ if: '$VARIABLE != null', when: 'delayed', start_in: '1 day' }, { if: '$CI_JOB_NAME == "rspec"', when: 'on_failure' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$CI_BUILD_NAME == "rspec"', when: 'on_failure' }]]
+ ]
+ end
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'always')
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_failure')
+ end
end
end
- end
- context 'with an explicit `when: on_failure`' do
- where(:rule_set) do
- [
- [[{ if: '$CI_JOB_NAME == "rspec" && $VAR == null', when: 'on_failure' }]],
- [[{ if: '$VARIABLE != null', when: 'delayed', start_in: '1 day' }, { if: '$CI_JOB_NAME == "rspec"', when: 'on_failure' }]],
- [[{ if: '$VARIABLE == "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$CI_BUILD_NAME == "rspec"', when: 'on_failure' }]]
- ]
+ context 'with an explicit `when: delayed`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }]],
+ [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'delayed', options: { start_in: '1 day' })
+ end
+ end
end
- with_them do
- it { is_expected.to be_included }
+ context 'without an explicit when: value' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null' }]],
+ [[{ if: '$VARIABLE == null' }, { if: '$VARIABLE == null' }]],
+ [[{ if: '$VARIABLE != "the wrong value"' }, { if: '$VARIABLE == null' }]]
+ ]
+ end
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'on_failure')
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_success')
+ end
end
end
end
- context 'with an explicit `when: delayed`' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }]],
- [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]],
- [[{ if: '$VARIABLE != "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]]
- ]
+ context 'with a matching changes: rule' do
+ let(:pipeline) do
+ build(:ci_pipeline, project: project).tap do |pipeline|
+ stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml])
+ end
end
- with_them do
- it { is_expected.to be_included }
+ context 'with an explicit `when: never`' do
+ where(:rule_set) do
+ [
+ [[{ changes: { paths: %w[*/**/*.rb] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'never' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[*.yml] }, when: 'never' }, { changes: { paths: %w[*.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[.*.yml] }, when: 'never' }, { changes: { paths: %w[.*.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[**/*] }, when: 'never' }, { changes: { paths: %w[**/*] }, when: 'always' }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[.*.yml **/*] }, when: 'never' }, { changes: { paths: %w[.*.yml **/*] }, when: 'always' }]]
+ ]
+ end
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'delayed', options: { start_in: '1 day' })
+ with_them do
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
end
end
- end
- context 'without an explicit when: value' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE == null' }]],
- [[{ if: '$VARIABLE == null' }, { if: '$VARIABLE == null' }]],
- [[{ if: '$VARIABLE != "the wrong value"' }, { if: '$VARIABLE == null' }]]
- ]
- end
+ context 'with an explicit `when: always`' do
+ where(:rule_set) do
+ [
+ [[{ changes: { paths: %w[*/**/*.rb] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'always' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[*.yml] }, when: 'always' }, { changes: { paths: %w[*.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[.*.yml] }, when: 'always' }, { changes: { paths: %w[.*.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[**/*] }, when: 'always' }, { changes: { paths: %w[**/*] }, when: 'never' }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[.*.yml **/*] }, when: 'always' }, { changes: { paths: %w[.*.yml **/*] }, when: 'never' }]]
+ ]
+ end
- with_them do
- it { is_expected.to be_included }
+ with_them do
+ it { is_expected.to be_included }
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'on_success')
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'always')
+ end
end
end
- end
- end
- context 'with a matching changes: rule' do
- let(:pipeline) do
- build(:ci_pipeline, project: project).tap do |pipeline|
- stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml])
+ context 'without an explicit when: value' do
+ where(:rule_set) do
+ [
+ [[{ changes: { paths: %w[*/**/*.rb] } }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] } }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] } }]],
+ [[{ changes: { paths: %w[*.yml] } }]],
+ [[{ changes: { paths: %w[.*.yml] } }]],
+ [[{ changes: { paths: %w[**/*] } }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] } }]],
+ [[{ changes: { paths: %w[.*.yml **/*] } }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_success')
+ end
+ end
end
end
- context 'with an explicit `when: never`' do
+ context 'with no matching rule' do
where(:rule_set) do
[
- [[{ changes: { paths: %w[*/**/*.rb] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb] }, when: 'always' }]],
- [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }]],
- [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'never' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'always' }]],
- [[{ changes: { paths: %w[*.yml] }, when: 'never' }, { changes: { paths: %w[*.yml] }, when: 'always' }]],
- [[{ changes: { paths: %w[.*.yml] }, when: 'never' }, { changes: { paths: %w[.*.yml] }, when: 'always' }]],
- [[{ changes: { paths: %w[**/*] }, when: 'never' }, { changes: { paths: %w[**/*] }, when: 'always' }]],
- [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }]],
- [[{ changes: { paths: %w[.*.yml **/*] }, when: 'never' }, { changes: { paths: %w[.*.yml **/*] }, when: 'always' }]]
+ [[{ if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE != null', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE != null', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE != null' }]],
+ [[{ if: '$VARIABLE != null' }, { if: '$VARIABLE != null' }]],
+ [[{ if: '$VARIABLE == "the wrong value"' }, { if: '$VARIABLE != null' }]]
]
end
@@ -878,257 +971,249 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
- context 'with an explicit `when: always`' do
- where(:rule_set) do
- [
- [[{ changes: { paths: %w[*/**/*.rb] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb] }, when: 'never' }]],
- [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }]],
- [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'always' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'never' }]],
- [[{ changes: { paths: %w[*.yml] }, when: 'always' }, { changes: { paths: %w[*.yml] }, when: 'never' }]],
- [[{ changes: { paths: %w[.*.yml] }, when: 'always' }, { changes: { paths: %w[.*.yml] }, when: 'never' }]],
- [[{ changes: { paths: %w[**/*] }, when: 'always' }, { changes: { paths: %w[**/*] }, when: 'never' }]],
- [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }]],
- [[{ changes: { paths: %w[.*.yml **/*] }, when: 'always' }, { changes: { paths: %w[.*.yml **/*] }, when: 'never' }]]
- ]
+ context 'with a rule using CI_ENVIRONMENT_NAME variable' do
+ let(:rule_set) do
+ [{ if: '$CI_ENVIRONMENT_NAME == "test"' }]
end
- with_them do
+ context 'when environment:name satisfies the rule' do
+ let(:attributes) { { name: 'rspec', rules: rule_set, environment: 'test', when: 'on_success' } }
+
it { is_expected.to be_included }
it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'always')
+ expect(seed_build.attributes).to include(when: 'on_success')
end
end
- end
- context 'without an explicit when: value' do
- where(:rule_set) do
- [
- [[{ changes: { paths: %w[*/**/*.rb] } }]],
- [[{ changes: { paths: %w[app/models/ci/pipeline.rb] } }]],
- [[{ changes: { paths: %w[spec/**/*.rb] } }]],
- [[{ changes: { paths: %w[*.yml] } }]],
- [[{ changes: { paths: %w[.*.yml] } }]],
- [[{ changes: { paths: %w[**/*] } }]],
- [[{ changes: { paths: %w[*/**/*.rb *.yml] } }]],
- [[{ changes: { paths: %w[.*.yml **/*] } }]]
- ]
+ context 'when environment:name does not satisfy rule' do
+ let(:attributes) { { name: 'rspec', rules: rule_set, environment: 'dev', when: 'on_success' } }
+
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
end
- with_them do
- it { is_expected.to be_included }
+ context 'when environment:name is not set' do
+ it { is_expected.not_to be_included }
it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'on_success')
+ expect(seed_build.attributes).to include(when: 'never')
end
end
end
- end
- context 'with no matching rule' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE != null', when: 'never' }]],
- [[{ if: '$VARIABLE != null', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
- [[{ if: '$VARIABLE == "the wrong value"', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
- [[{ if: '$VARIABLE != null', when: 'always' }]],
- [[{ if: '$VARIABLE != null', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
- [[{ if: '$VARIABLE == "the wrong value"', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
- [[{ if: '$VARIABLE != null' }]],
- [[{ if: '$VARIABLE != null' }, { if: '$VARIABLE != null' }]],
- [[{ if: '$VARIABLE == "the wrong value"' }, { if: '$VARIABLE != null' }]]
- ]
+ context 'with no rules' do
+ let(:rule_set) { [] }
+
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
end
- with_them do
+ context 'with invalid rules raising error' do
+ let(:rule_set) do
+ [
+ { changes: { paths: ['README.md'], compare_to: 'invalid-ref' }, when: 'never' }
+ ]
+ end
+
it { is_expected.not_to be_included }
it 'correctly populates when:' do
expect(seed_build.attributes).to include(when: 'never')
end
+
+ it 'returns an error' do
+ expect(seed_build.errors).to contain_exactly(
+ 'Failed to parse rule for rspec: rules:changes:compare_to is not a valid ref'
+ )
+ end
end
end
+ end
- context 'with no rules' do
- let(:rule_set) { [] }
+ describe 'applying needs: dependency' do
+ subject { seed_build }
- it { is_expected.not_to be_included }
+ let(:needs_count) { 1 }
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'never')
- end
+ let(:needs_attributes) do
+ Array.new(needs_count, name: 'build')
end
- context 'with invalid rules raising error' do
- let(:rule_set) do
- [
- { changes: { paths: ['README.md'], compare_to: 'invalid-ref' }, when: 'never' }
- ]
- end
+ let(:attributes) do
+ {
+ name: 'rspec',
+ needs_attributes: needs_attributes
+ }
+ end
- it { is_expected.not_to be_included }
+ context 'when build job is not present in prior stages' do
+ it "is included" do
+ is_expected.to be_included
+ end
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'never')
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "'rspec' job needs 'build' job, but 'build' is not in any previous stage")
end
- it 'returns an error' do
- expect(seed_build.errors).to contain_exactly(
- 'Failed to parse rule for rspec: rules:changes:compare_to is not a valid ref'
- )
+ 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
end
end
- end
- end
- describe 'applying needs: dependency' do
- subject { seed_build }
+ context 'when build job is part of prior stages' do
+ let(:stage_attributes) do
+ {
+ name: 'build',
+ index: 0,
+ builds: [{ name: 'build' }]
+ }
+ end
- let(:needs_count) { 1 }
+ let(:stage_seed) do
+ Gitlab::Ci::Pipeline::Seed::Stage.new(seed_context, stage_attributes, [])
+ end
- let(:needs_attributes) do
- Array.new(needs_count, name: 'build')
- end
+ let(:previous_stages) { [stage_seed] }
- let(:attributes) do
- {
- name: 'rspec',
- needs_attributes: needs_attributes
- }
- end
+ it "is included" do
+ is_expected.to be_included
+ end
- context 'when build job is not present in prior stages' do
- it "is included" do
- is_expected.to be_included
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
+ end
end
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- "'rspec' job needs 'build' job, but 'build' is not in any previous stage")
- end
+ context 'when build job is part of the same stage' do
+ let(:current_stage) { double(seeds_names: [attributes[:name], 'build']) }
- context 'when the needed job is optional' do
- let(:needs_attributes) { [{ name: 'build', optional: true }] }
+ it 'is included' do
+ is_expected.to be_included
+ end
- it "does not return an error" do
+ it 'does not have errors' do
expect(subject.errors).to be_empty
end
end
- end
-
- context 'when build job is part of prior stages' do
- let(:stage_attributes) do
- {
- name: 'build',
- index: 0,
- builds: [{ name: 'build' }]
- }
- end
-
- let(:stage_seed) do
- Gitlab::Ci::Pipeline::Seed::Stage.new(seed_context, stage_attributes, [])
- end
- let(:previous_stages) { [stage_seed] }
+ context 'when using 101 needs' do
+ let(:needs_count) { 101 }
- it "is included" do
- is_expected.to be_included
- end
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 50 others, but you have listed 101. See needs keyword documentation for more details")
+ end
- it "does not have errors" do
- expect(subject.errors).to be_empty
- end
- end
+ context 'when ci_needs_size_limit is set to 100' do
+ before do
+ project.actual_limits.update!(ci_needs_size_limit: 100)
+ end
- context 'when build job is part of the same stage' do
- let(:current_stage) { double(seeds_names: [attributes[:name], 'build']) }
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 100 others, but you have listed 101. See needs keyword documentation for more details")
+ end
+ end
- it 'is included' do
- is_expected.to be_included
- end
+ context 'when ci_needs_size_limit is set to 0' do
+ before do
+ project.actual_limits.update!(ci_needs_size_limit: 0)
+ end
- it 'does not have errors' do
- expect(subject.errors).to be_empty
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 0 others, but you have listed 101. See needs keyword documentation for more details")
+ end
+ end
end
end
- context 'when using 101 needs' do
- let(:needs_count) { 101 }
+ describe 'applying pipeline variables' do
+ subject { seed_build }
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- "rspec: one job can only need 50 others, but you have listed 101. See needs keyword documentation for more details")
+ let(:pipeline_variables) { [] }
+ let(:pipeline) do
+ build(:ci_empty_pipeline, project: project, sha: head_sha, variables: pipeline_variables)
end
- context 'when ci_needs_size_limit is set to 100' do
- before do
- project.actual_limits.update!(ci_needs_size_limit: 100)
+ 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
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- "rspec: one job can only need 100 others, but you have listed 101. See needs keyword documentation for more details")
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
end
end
- context 'when ci_needs_size_limit is set to 0' do
- before do
- project.actual_limits.update!(ci_needs_size_limit: 0)
+ 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
it "returns an error" do
expect(subject.errors).to contain_exactly(
- "rspec: one job can only need 0 others, but you have listed 101. See needs keyword documentation for more details")
+ '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
- 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)
+ describe 'feature flag ci_reuse_build_in_seed_context' do
+ let(:attributes) do
+ { name: 'rspec', rules: [{ if: '$VARIABLE == null' }], when: 'on_success' }
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 enabled' do
+ it_behaves_like 'build seed'
- it "does not have errors" do
- expect(subject.errors).to be_empty
+ it 'initializes the build once' do
+ expect(Ci::Build).to receive(:new).once.and_call_original
+ seed_build.to_resource
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
-
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- 'rspec: circular variable reference detected: ["A", "B", "C"]')
+ context 'when disabled' do
+ before do
+ stub_feature_flags(ci_reuse_build_in_seed_context: false)
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_behaves_like 'build seed'
- it "included? returns true" do
- expect(subject.included?).to eq(true)
- end
+ it 'initializes the build twice' do
+ expect(Ci::Build).to receive(:new).twice.and_call_original
+ seed_build.to_resource
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index a632b5dedcf..288ac3f3854 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Seed::Stage do
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Stage, feature_category: :pipeline_authoring do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:previous_stages) { [] }
diff --git a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
index cdaf9354104..5dbcc1991d4 100644
--- a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
+++ b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Reports::Sbom::Component do
+RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependency_management do
let(:component_type) { 'library' }
let(:name) { 'component-name' }
let(:purl_type) { 'npm' }
diff --git a/spec/lib/gitlab/ci/reports/sbom/report_spec.rb b/spec/lib/gitlab/ci/reports/sbom/report_spec.rb
index f9a83378f46..5d281f6ed76 100644
--- a/spec/lib/gitlab/ci/reports/sbom/report_spec.rb
+++ b/spec/lib/gitlab/ci/reports/sbom/report_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Reports::Sbom::Report do
+RSpec.describe Gitlab::Ci::Reports::Sbom::Report, feature_category: :dependency_management do
subject(:report) { described_class.new }
describe '#valid?' do
diff --git a/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb b/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb
index 75ea91251eb..4fb766d7d38 100644
--- a/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Reports::Sbom::Reports do
+RSpec.describe Gitlab::Ci::Reports::Sbom::Reports, feature_category: :dependency_management do
subject(:reports_list) { described_class.new }
describe '#add_report' do
diff --git a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
index 343c0d8c15c..63b8e5fdf01 100644
--- a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
+++ b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Ci::Reports::Sbom::Source do
+RSpec.describe Gitlab::Ci::Reports::Sbom::Source, feature_category: :dependency_management do
let(:attributes) do
{
type: :dependency_scanning,
diff --git a/spec/lib/gitlab/ci/reports/security/reports_spec.rb b/spec/lib/gitlab/ci/reports/security/reports_spec.rb
index 33f3317c655..cb6a91655ed 100644
--- a/spec/lib/gitlab/ci/reports/security/reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/security/reports_spec.rb
@@ -52,105 +52,4 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do
it { is_expected.to match_array(expected_findings) }
end
-
- describe "#violates_default_policy_against?" do
- let(:high_severity_dast) { build(:ci_reports_security_finding, severity: 'high', report_type: 'dast') }
- let(:vulnerabilities_allowed) { 0 }
- let(:severity_levels) { %w(critical high) }
- let(:vulnerability_states) { %w(newly_detected) }
-
- subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states) }
-
- before do
- security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
- end
-
- context 'when the target_reports is `nil`' do
- let(:target_reports) { nil }
-
- context 'with severity levels matching the existing vulnerabilities' do
- it { is_expected.to be(true) }
- end
-
- context "without any severity levels matching the existing vulnerabilities" do
- let(:severity_levels) { %w(critical) }
-
- it { is_expected.to be(false) }
- end
- end
-
- context 'when the target_reports is not `nil`' do
- let(:target_reports) { described_class.new(pipeline) }
-
- context "when a report has a new unsafe vulnerability" do
- context 'with severity levels matching the existing vulnerabilities' do
- it { is_expected.to be(true) }
- end
-
- it { is_expected.to be(true) }
-
- context 'with vulnerabilities_allowed higher than the number of new vulnerabilities' do
- let(:vulnerabilities_allowed) { 10000 }
-
- it { is_expected.to be(false) }
- end
-
- context "without any severity levels matching the existing vulnerabilities" do
- let(:severity_levels) { %w(critical) }
-
- it { is_expected.to be(false) }
- end
- end
-
- context "when none of the reports have a new unsafe vulnerability" do
- before do
- target_reports.get_report('sast', artifact).add_finding(high_severity_dast)
- end
-
- it { is_expected.to be(false) }
- end
-
- context 'with related report_types' do
- let(:report_types) { %w(dast sast) }
-
- subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states, report_types) }
-
- it { is_expected.to be(true) }
- end
-
- context 'with unrelated report_types' do
- let(:report_types) { %w(dependency_scanning sast) }
-
- subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states, report_types) }
-
- it { is_expected.to be(false) }
- end
-
- context 'when target_reports is not nil and reports is empty' do
- let(:without_reports) { described_class.new(pipeline) }
-
- subject { without_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states) }
-
- before do
- target_reports.get_report('sast', artifact).add_finding(high_severity_dast)
- end
-
- context 'when require_approval_on_scan_removal feature is enabled' do
- before do
- stub_feature_flags(require_approval_on_scan_removal: true)
- end
-
- it { is_expected.to be(true) }
- end
-
- context 'when require_approval_on_scan_removal feature is disabled' do
- before do
- stub_feature_flags(require_approval_on_scan_removal: false)
- end
-
- it { is_expected.to be(false) }
- end
- end
- end
- end
end
diff --git a/spec/lib/gitlab/ci/runner_instructions_spec.rb b/spec/lib/gitlab/ci/runner_instructions_spec.rb
index f872c631a50..56f69720b87 100644
--- a/spec/lib/gitlab/ci/runner_instructions_spec.rb
+++ b/spec/lib/gitlab/ci/runner_instructions_spec.rb
@@ -69,6 +69,7 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
'windows' | 'amd64'
'windows' | '386'
'osx' | 'amd64'
+ 'osx' | 'arm64'
end
with_them do
diff --git a/spec/lib/gitlab/ci/trace/archive_spec.rb b/spec/lib/gitlab/ci/trace/archive_spec.rb
index f91cb03883a..582c4ad343f 100644
--- a/spec/lib/gitlab/ci/trace/archive_spec.rb
+++ b/spec/lib/gitlab/ci/trace/archive_spec.rb
@@ -75,15 +75,6 @@ RSpec.describe Gitlab::Ci::Trace::Archive do
include_context 'with FIPS'
end
- context 'with background_upload enabled' do
- before do
- stub_artifacts_object_storage(background_upload: true)
- end
-
- it_behaves_like 'skips validations'
- include_context 'with FIPS'
- end
-
context 'with direct_upload enabled' do
before do
stub_artifacts_object_storage(direct_upload: true)
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
index 52ba85d2df1..5aa752ee429 100644
--- a/spec/lib/gitlab/ci/variables/builder_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache, feature_category: :pipeline_authoring do
include Ci::TemplateHelpers
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, namespace: group) }
@@ -13,7 +13,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do
name: 'rspec:test 1',
pipeline: pipeline,
user: user,
- yaml_variables: [{ key: 'YAML_VARIABLE', value: 'value' }]
+ yaml_variables: [{ key: 'YAML_VARIABLE', value: 'value' }],
+ environment: 'test'
)
end
@@ -32,6 +33,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do
value: job.stage_name },
{ key: 'CI_NODE_TOTAL',
value: '1' },
+ { key: 'CI_ENVIRONMENT_NAME',
+ value: 'test' },
{ key: 'CI_BUILD_NAME',
value: 'rspec:test 1' },
{ key: 'CI_BUILD_STAGE',
@@ -76,6 +79,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do
value: project.full_path_slug },
{ key: 'CI_PROJECT_NAMESPACE',
value: project.namespace.full_path },
+ { key: 'CI_PROJECT_NAMESPACE_ID',
+ value: project.namespace.id.to_s },
{ key: 'CI_PROJECT_ROOT_NAMESPACE',
value: project.namespace.root_ancestor.path },
{ key: 'CI_PROJECT_URL',
@@ -276,11 +281,17 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do
subject { builder.kubernetes_variables(environment: nil, job: job) }
before do
- allow(Ci::GenerateKubeconfigService).to receive(:new).with(job.pipeline, token: job.token).and_return(service)
+ allow(Ci::GenerateKubeconfigService).to receive(:new).with(job.pipeline, token: job.token, environment: anything).and_return(service)
end
it { is_expected.to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) }
+ it 'calls the GenerateKubeconfigService with the correct arguments' do
+ expect(Ci::GenerateKubeconfigService).to receive(:new).with(job.pipeline, token: job.token, environment: nil)
+
+ subject
+ end
+
context 'generated config is invalid' do
let(:template_valid) { false }
@@ -297,6 +308,16 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do
expect(subject['KUBECONFIG'].value).to eq('example-kubeconfig')
expect(subject['OTHER'].value).to eq('some value')
end
+
+ context 'when environment is not nil' do
+ subject { builder.kubernetes_variables(environment: 'production', job: job) }
+
+ it 'passes the environment when generating the KUBECONFIG' do
+ expect(Ci::GenerateKubeconfigService).to receive(:new).with(job.pipeline, token: job.token, environment: 'production')
+
+ subject
+ end
+ end
end
describe '#deployment_variables' do
diff --git a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
index 7f203168706..5c9f156e054 100644
--- a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
@@ -12,6 +12,20 @@ module Gitlab
let(:ci_config) { Gitlab::Ci::Config.new(config_content, user: user) }
let(:result) { described_class.new(ci_config: ci_config, warnings: ci_config&.warnings) }
+ describe '#builds' do
+ context 'when a job has ID tokens' do
+ let(:config_content) do
+ YAML.dump(
+ test: { stage: 'test', script: 'echo', id_tokens: { TEST_ID_TOKEN: { aud: 'https://gitlab.com' } } }
+ )
+ end
+
+ it 'includes `id_tokens`' do
+ expect(result.builds.first[:id_tokens]).to eq({ TEST_ID_TOKEN: { aud: 'https://gitlab.com' } })
+ end
+ end
+ end
+
describe '#config_metadata' do
subject(:config_metadata) { result.config_metadata }
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 5de813f7739..ae98d2e0cad 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -870,6 +870,69 @@ module Gitlab
end
end
end
+
+ describe "hooks" do
+ context 'when it is a simple script' do
+ let(:config) do
+ {
+ test: { script: ["script"],
+ hooks: { pre_get_sources_script: ["echo 1", "echo 2", "pwd"] } }
+ }
+ end
+
+ it "returns hooks in options" do
+ expect(subject[:options][:hooks]).to eq(
+ { pre_get_sources_script: ["echo 1", "echo 2", "pwd"] }
+ )
+ end
+ end
+
+ context 'when it is nested arrays of strings' do
+ let(:config) do
+ {
+ test: { script: ["script"],
+ hooks: { pre_get_sources_script: [[["global script"], "echo 1"], "echo 2", ["ls"], "pwd"] } }
+ }
+ end
+
+ it "returns hooks in options" do
+ expect(subject[:options][:hooks]).to eq(
+ { pre_get_sources_script: ["global script", "echo 1", "echo 2", "ls", "pwd"] }
+ )
+ end
+ end
+
+ context 'when receiving from the default' do
+ let(:config) do
+ {
+ default: { hooks: { pre_get_sources_script: ["echo 1", "echo 2", "pwd"] } },
+ test: { script: ["script"] }
+ }
+ end
+
+ it "inherits hooks" do
+ expect(subject[:options][:hooks]).to eq(
+ { pre_get_sources_script: ["echo 1", "echo 2", "pwd"] }
+ )
+ end
+ end
+
+ context 'when overriding the default' do
+ let(:config) do
+ {
+ default: { hooks: { pre_get_sources_script: ["echo 1", "echo 2", "pwd"] } },
+ test: { script: ["script"],
+ hooks: { pre_get_sources_script: ["echo 3", "echo 4", "pwd"] } }
+ }
+ end
+
+ it "overrides hooks" do
+ expect(subject[:options][:hooks]).to eq(
+ { pre_get_sources_script: ["echo 3", "echo 4", "pwd"] }
+ )
+ end
+ end
+ end
end
describe "Image and service handling" do
@@ -2883,7 +2946,7 @@ module Gitlab
context 'returns errors if job artifacts:when is not an a predefined value' do
let(:config) { YAML.dump({ stages: %w(build test), rspec: { script: "test", artifacts: { when: 1 } } }) }
- it_behaves_like 'returns errors', 'jobs:rspec:artifacts when should be on_success, on_failure or always'
+ it_behaves_like 'returns errors', 'jobs:rspec:artifacts when should be one of: on_success, on_failure, always'
end
context 'returns errors if job artifacts:expire_in is not an a string' do