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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/components/instance_path_spec.rb186
-rw-r--r--spec/lib/gitlab/ci/config/entry/auto_cancel_spec.rb72
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb9
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/config/entry/includes_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb89
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb31
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/entry/retry_spec.rb86
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb59
-rw-r--r--spec/lib/gitlab/ci/config/entry/workflow_spec.rb110
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/text_interpolator_spec.rb221
-rw-r--r--spec/lib/gitlab/ci/config/interpolation/text_template_spec.rb105
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/jwt_v2_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb73
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb268
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb64
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_metadata_spec.rb172
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/source_spec.rb102
-rw-r--r--spec/lib/gitlab/ci/runner_instructions_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/runner_releases_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/runner_upgrade_check_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Diffblue_Cover_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/templates/templates_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/variables/downstream/generator_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/yaml_processor/test_cases/interruptible_spec.rb96
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb110
40 files changed, 1811 insertions, 453 deletions
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 4895077a731..f8c0d69be2e 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
context 'when image is defined in job' do
let(:image_name) { 'image:1.0' }
- let(:job) { create(:ci_build, options: { image: image_name } ) }
+ let(:job) { create(:ci_build, options: { image: image_name }) }
context 'when image is defined as string' do
it 'fabricates an object of the proper class' do
@@ -29,12 +29,14 @@ RSpec.describe Gitlab::Ci::Build::Image do
context 'when image is defined as hash' do
let(:entrypoint) { '/bin/sh' }
let(:pull_policy) { %w[always if-not-present] }
+ let(:executor_opts) { { docker: { platform: 'arm64' } } }
let(:job) do
create(:ci_build, options: { image: { name: image_name,
entrypoint: entrypoint,
ports: [80],
- pull_policy: pull_policy } } )
+ executor_opts: executor_opts,
+ pull_policy: pull_policy } })
end
it 'fabricates an object of the proper class' do
@@ -44,6 +46,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
it 'populates fabricated object with the proper attributes' do
expect(subject.name).to eq(image_name)
expect(subject.entrypoint).to eq(entrypoint)
+ expect(subject.executor_opts).to eq(executor_opts)
expect(subject.pull_policy).to eq(pull_policy)
end
@@ -98,11 +101,12 @@ RSpec.describe Gitlab::Ci::Build::Image do
let(:service_entrypoint) { '/bin/sh' }
let(:service_alias) { 'db' }
let(:service_command) { 'sleep 30' }
+ let(:executor_opts) { { docker: { platform: 'amd64' } } }
let(:pull_policy) { %w[always if-not-present] }
let(:job) do
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
alias: service_alias, command: service_command, ports: [80],
- pull_policy: pull_policy }] })
+ executor_opts: executor_opts, pull_policy: pull_policy }] })
end
it 'fabricates an non-empty array of objects' do
@@ -116,6 +120,7 @@ RSpec.describe Gitlab::Ci::Build::Image do
expect(subject.first.entrypoint).to eq(service_entrypoint)
expect(subject.first.alias).to eq(service_alias)
expect(subject.first.command).to eq(service_command)
+ expect(subject.first.executor_opts).to eq(executor_opts)
expect(subject.first.pull_policy).to eq(pull_policy)
port = subject.first.ports.first
diff --git a/spec/lib/gitlab/ci/components/instance_path_spec.rb b/spec/lib/gitlab/ci/components/instance_path_spec.rb
index 4ba963b54b5..b9b4c3f7c69 100644
--- a/spec/lib/gitlab/ci/components/instance_path_spec.rb
+++ b/spec/lib/gitlab/ci/components/instance_path_spec.rb
@@ -42,48 +42,86 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
end
end
- context 'when the component is simple (single file template)' do
- it 'fetches the component content', :aggregate_failures do
+ shared_examples 'does not find the component' do
+ it 'returns nil' do
result = path.fetch_content!(current_user: user)
- expect(result.content).to eq('image: alpine_1')
- expect(result.path).to eq('templates/secret-detection.yml')
- expect(path.host).to eq(current_host)
- expect(path.project).to eq(project)
- expect(path.sha).to eq(project.commit('master').id)
+ expect(result).to be_nil
+ end
+ end
+
+ shared_examples 'finds the component' do
+ shared_examples 'fetches the component content' do
+ it 'fetches the component content', :aggregate_failures do
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq(file_content)
+ expect(result.path).to eq(file_path)
+ expect(path.host).to eq(current_host)
+ expect(path.project).to eq(project)
+ expect(path.sha).to eq(project.commit('master').id)
+ end
+ end
+
+ it_behaves_like 'fetches the component content'
+
+ context 'when feature flag ci_redirect_component_project is disabled' do
+ before do
+ stub_feature_flags(ci_redirect_component_project: false)
+ end
+
+ it_behaves_like 'fetches the component content'
+ end
+
+ context 'when the there is a redirect set for the project' do
+ let!(:redirect_route) { project.redirect_routes.create!(path: 'another-group/new-project') }
+ let(:project_path) { redirect_route.path }
+
+ it_behaves_like 'fetches the component content'
+
+ context 'when feature flag ci_redirect_component_project is disabled' do
+ before do
+ stub_feature_flags(ci_redirect_component_project: false)
+ end
+
+ it_behaves_like 'does not find the component'
+ end
+ end
+ end
+
+ context 'when the component is simple (single file template)' do
+ it_behaves_like 'finds the component' do
+ let(:file_path) { 'templates/secret-detection.yml' }
+ let(:file_content) { 'image: alpine_1' }
end
end
context 'when the component is complex (directory-based template)' do
let(:address) { "acme.com/#{project_path}/dast@#{version}" }
- it 'fetches the component content', :aggregate_failures do
- result = path.fetch_content!(current_user: user)
- expect(result.content).to eq('image: alpine_2')
- expect(result.path).to eq('templates/dast/template.yml')
- expect(path.host).to eq(current_host)
- expect(path.project).to eq(project)
- expect(path.sha).to eq(project.commit('master').id)
+ it_behaves_like 'finds the component' do
+ let(:file_path) { 'templates/dast/template.yml' }
+ let(:file_content) { 'image: alpine_2' }
end
context 'when there is an invalid nested component folder' do
let(:address) { "acme.com/#{project_path}/dast/another-folder@#{version}" }
- it 'returns nil' do
- result = path.fetch_content!(current_user: user)
- expect(result.content).to be_nil
- end
+ it_behaves_like 'does not find the component'
end
context 'when there is an invalid nested component path' do
let(:address) { "acme.com/#{project_path}/dast/another-template@#{version}" }
- it 'returns nil' do
- result = path.fetch_content!(current_user: user)
- expect(result.content).to be_nil
- end
+ it_behaves_like 'does not find the component'
end
end
+ context "when the project path starts with '/'" do
+ let(:project_path) { "/#{project.full_path}" }
+
+ it_behaves_like 'does not find the component'
+ end
+
+ # TODO: remove when deleting the feature flag `ci_redirect_component_project`
shared_examples 'prevents infinite loop' do |prefix|
context "when the project path starts with '#{prefix}'" do
let(:project_path) { "#{prefix}#{project.full_path}" }
@@ -127,7 +165,7 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
released_at: Time.zone.now)
end
- it 'fetches the component content', :aggregate_failures do
+ it 'returns the component content of the latest project release', :aggregate_failures do
result = path.fetch_content!(current_user: user)
expect(result.content).to eq('image: alpine_2')
expect(result.path).to eq('templates/secret-detection.yml')
@@ -135,6 +173,25 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
expect(path.project).to eq(project)
expect(path.sha).to eq(latest_sha)
end
+
+ context 'when the project is a catalog resource' do
+ let_it_be(:resource) { create(:ci_catalog_resource, project: project) }
+
+ before do
+ project.releases.each do |release|
+ create(:ci_catalog_resource_version, catalog_resource: resource, release: release)
+ end
+ end
+
+ it 'returns the component content of the latest catalog resource version', :aggregate_failures do
+ result = path.fetch_content!(current_user: user)
+ expect(result.content).to eq('image: alpine_2')
+ expect(result.path).to eq('templates/secret-detection.yml')
+ expect(path.host).to eq(current_host)
+ expect(path.project).to eq(project)
+ expect(path.sha).to eq(latest_sha)
+ end
+ end
end
context 'when version does not exist' do
@@ -162,88 +219,5 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
end
end
end
-
- # All the following tests are for deprecated code and will be removed
- # in https://gitlab.com/gitlab-org/gitlab/-/issues/415855
- context 'when the project does not contain a templates directory' do
- let(:project_path) { project.full_path }
- let(:address) { "acme.com/#{project_path}/component@#{version}" }
-
- let_it_be(:project) do
- create(
- :project, :custom_repo,
- files: {
- 'component/template.yml' => 'image: alpine'
- }
- )
- end
-
- before do
- project.add_developer(user)
- end
-
- it 'fetches the component content', :aggregate_failures do
- result = path.fetch_content!(current_user: user)
- expect(result.content).to eq('image: alpine')
- expect(result.path).to eq('component/template.yml')
- expect(path.host).to eq(current_host)
- expect(path.project).to eq(project)
- expect(path.sha).to eq(project.commit('master').id)
- end
-
- context 'when project path is nested under a subgroup' do
- let_it_be(:group) { create(:group, :nested) }
- let_it_be(:project) do
- create(
- :project, :custom_repo,
- files: {
- 'component/template.yml' => 'image: alpine'
- },
- group: group
- )
- end
-
- it 'fetches the component content', :aggregate_failures do
- result = path.fetch_content!(current_user: user)
- expect(result.content).to eq('image: alpine')
- expect(result.path).to eq('component/template.yml')
- expect(path.host).to eq(current_host)
- expect(path.project).to eq(project)
- expect(path.sha).to eq(project.commit('master').id)
- end
- end
-
- context 'when current GitLab instance is installed on a relative URL' do
- let(:address) { "acme.com/gitlab/#{project_path}/component@#{version}" }
- let(:current_host) { 'acme.com/gitlab/' }
-
- it 'fetches the component content', :aggregate_failures do
- result = path.fetch_content!(current_user: user)
- expect(result.content).to eq('image: alpine')
- expect(result.path).to eq('component/template.yml')
- expect(path.host).to eq(current_host)
- expect(path.project).to eq(project)
- expect(path.sha).to eq(project.commit('master').id)
- end
- end
-
- context 'when version does not exist' do
- let(:version) { 'non-existent' }
-
- it 'returns nil', :aggregate_failures do
- expect(path.fetch_content!(current_user: user)).to be_nil
- expect(path.host).to eq(current_host)
- expect(path.project).to eq(project)
- expect(path.sha).to be_nil
- end
- end
-
- context 'when user does not have permissions' do
- it 'raises an error when fetching the content' do
- expect { path.fetch_content!(current_user: build(:user)) }
- .to raise_error(Gitlab::Access::AccessDeniedError)
- end
- end
- end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/auto_cancel_spec.rb b/spec/lib/gitlab/ci/config/entry/auto_cancel_spec.rb
new file mode 100644
index 00000000000..bdd66cc00a1
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/auto_cancel_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Entry::AutoCancel, feature_category: :pipeline_composition do
+ subject(:config) { described_class.new(config_hash) }
+
+ context 'with on_new_commit' do
+ let(:config_hash) do
+ { on_new_commit: 'interruptible' }
+ end
+
+ it { is_expected.to be_valid }
+
+ it 'returns value correctly' do
+ expect(config.value).to eq(config_hash)
+ end
+
+ context 'when on_new_commit is invalid' do
+ let(:config_hash) do
+ { on_new_commit: 'invalid' }
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns errors' do
+ expect(config.errors)
+ .to include('auto cancel on new commit must be one of: conservative, interruptible, disabled')
+ end
+ end
+ end
+
+ context 'with on_job_failure' do
+ ['all', 'none', nil].each do |value|
+ context 'when the `on_job_failure` value is valid' do
+ let(:config_hash) { { on_job_failure: value } }
+
+ it { is_expected.to be_valid }
+
+ it 'returns value correctly' do
+ expect(config.value).to eq(on_job_failure: value)
+ end
+ end
+ end
+
+ context 'when on_job_failure is invalid' do
+ let(:config_hash) do
+ { on_job_failure: 'invalid' }
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns errors' do
+ expect(config.errors)
+ .to include('auto cancel on job failure must be one of: none, all')
+ end
+ end
+ end
+
+ context 'with invalid key' do
+ let(:config_hash) do
+ { invalid: 'interruptible' }
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns errors' do
+ expect(config.errors)
+ .to include('auto cancel config contains unknown keys: invalid')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index 6e6b9d949c5..35f2a99ee87 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -2,10 +2,11 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
+RSpec.describe Gitlab::Ci::Config::Entry::Bridge, feature_category: :continuous_integration do
subject(:entry) { described_class.new(config, name: :my_bridge) }
it_behaves_like 'with inheritable CI config' do
+ let(:config) { { trigger: 'some/project' } }
let(:inheritable_key) { 'default' }
let(:inheritable_class) { Gitlab::Ci::Config::Entry::Default }
@@ -13,9 +14,13 @@ 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 hooks image services cache interruptible timeout
+ %i[before_script after_script hooks image services cache timeout
retry tags artifacts id_tokens]
end
+
+ before do
+ allow(entry).to receive_message_chain(:inherit_entry, :default_entry, :inherit?).and_return(true)
+ end
end
describe '.matching?' do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 17c45ec4c2c..99a6e25b313 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -42,6 +42,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
end
end
+ describe '#executor_opts' do
+ it "returns nil" do
+ expect(entry.executor_opts).to be_nil
+ end
+ end
+
describe '#ports' do
it "returns image's ports" do
expect(entry.ports).to be_nil
@@ -88,6 +94,54 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
end
end
+ context 'when configuration specifies docker' do
+ let(:config) { { name: 'image:1.0', docker: {} } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'image:1.0',
+ executor_opts: {
+ docker: {}
+ }
+ )
+ end
+ end
+
+ context "when docker specifies an option" do
+ let(:config) { { name: 'image:1.0', docker: { platform: 'amd64' } } }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'image:1.0',
+ executor_opts: {
+ docker: { platform: 'amd64' }
+ }
+ )
+ end
+ end
+ end
+
+ context "when docker specifies an invalid option" do
+ let(:config) { { name: 'image:1.0', docker: { platform: 1 } } }
+
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors.first)
+ .to match %r{image executor opts '/docker/platform' must be a valid 'string'}
+ end
+ end
+ end
+
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) { { name: 'image:1.0', entrypoint: %w[/bin/sh run], ports: ports } }
@@ -146,7 +200,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
expect(entry.errors.first)
- .to match /config should be a hash or a string/
+ .to match(/config should be a hash or a string/)
end
end
@@ -163,7 +217,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
expect(entry.errors.first)
- .to match /config contains unknown keys: non_existing/
+ .to match(/config contains unknown keys: non_existing/)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/includes_spec.rb b/spec/lib/gitlab/ci/config/entry/includes_spec.rb
index f1f28c24e70..54c02868010 100644
--- a/spec/lib/gitlab/ci/config/entry/includes_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/includes_spec.rb
@@ -13,4 +13,18 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Includes, feature_category: :pipelin
2.times { expect { described_class.new(config) }.not_to change { described_class.aspects.count } }
end
end
+
+ describe 'validations' do
+ let(:config) { [1, 2] }
+
+ let(:includes_entry) { described_class.new(config, max_size: 1) }
+
+ it 'returns invalid' do
+ expect(includes_entry).not_to be_valid
+ end
+
+ it 'returns the appropriate error' do
+ expect(includes_entry.errors).to include('includes config is too long (maximum is 1)')
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
index 7cd9b0acb99..c0d21385ce6 100644
--- a/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Inherit::Default do
false | false
%w[image] | true
%w[before_script] | false
+ '123' | false
end
with_them do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 24d3cac6616..073d8feaadd 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -789,7 +789,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo
hooks: { pre_get_sources_script: 'echo hello' } }
end
- it 'returns correct value' do
+ it 'returns correct values' do
expect(entry.value).to eq(
name: :rspec,
before_script: %w[ls pwd],
@@ -806,6 +806,93 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo
)
end
end
+
+ context 'with retry present in the config' do
+ let(:config) do
+ {
+ script: 'rspec',
+ retry: { max: 1, when: "always" }
+ }
+ end
+
+ it 'returns correct values' do
+ expect(entry.value)
+ .to eq(name: :rspec,
+ script: %w[rspec],
+ stage: 'test',
+ ignore: false,
+ retry: { max: 1, when: %w[always] },
+ only: { refs: %w[branches tags] },
+ job_variables: {},
+ root_variables_inheritance: true,
+ scheduling_type: :stage
+ )
+ end
+
+ context 'when ci_retry_on_exit_codes feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_retry_on_exit_codes: false)
+ end
+
+ it 'returns correct values' do
+ expect(entry.value)
+ .to eq(name: :rspec,
+ script: %w[rspec],
+ stage: 'test',
+ ignore: false,
+ retry: { max: 1, when: %w[always] },
+ only: { refs: %w[branches tags] },
+ job_variables: {},
+ root_variables_inheritance: true,
+ scheduling_type: :stage
+ )
+ end
+ end
+
+ context 'with exit_codes present' do
+ let(:config) do
+ {
+ script: 'rspec',
+ retry: { max: 1, when: "always", exit_codes: 255 }
+ }
+ end
+
+ it 'returns correct values' do
+ expect(entry.value)
+ .to eq(name: :rspec,
+ script: %w[rspec],
+ stage: 'test',
+ ignore: false,
+ retry: { max: 1, when: %w[always], exit_codes: [255] },
+ only: { refs: %w[branches tags] },
+ job_variables: {},
+ root_variables_inheritance: true,
+ scheduling_type: :stage
+ )
+ end
+
+ context 'when ci_retry_on_exit_codes feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_retry_on_exit_codes: false)
+ end
+
+ it 'returns correct values' do
+ expect(entry.value)
+ .to eq(name: :rspec,
+ script: %w[rspec],
+ stage: 'test',
+ ignore: false,
+ # Shouldn't include exit_codes
+ retry: { max: 1, when: %w[always] },
+ only: { refs: %w[branches tags] },
+ job_variables: {},
+ root_variables_inheritance: true,
+ scheduling_type: :stage
+ )
+ end
+ end
+ end
+ end
end
context 'when job is using tags' do
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index 44e2fdbac37..84a8fd827cb 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -217,6 +217,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable, feature_category: :pipeli
end
end
end
+
+ context 'when interruptible is not a boolean' do
+ let(:config) { { interruptible: 123 } }
+
+ it 'returns error about wrong value type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include "interruptible config should be a boolean value"
+ end
+ end
end
describe '#relevant?' do
@@ -462,6 +471,28 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable, feature_category: :pipeli
end
end
end
+
+ context 'with interruptible' do
+ context 'when interruptible is not defined' do
+ let(:config) { { script: 'ls' } }
+
+ it 'sets interruptible to nil' do
+ entry.compose!(deps)
+
+ expect(entry.value[:interruptible]).to be_nil
+ end
+ end
+
+ context 'when interruptible is defined' do
+ let(:config) { { script: 'ls', interruptible: true } }
+
+ it 'sets interruptible to the value' do
+ entry.compose!(deps)
+
+ expect(entry.value[:interruptible]).to eq(true)
+ end
+ end
+ end
end
context 'when composed' do
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index d610c3ce2f6..a6675229c62 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -49,6 +49,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports, feature_category: :pipeline_c
:accessibility | 'gl-accessibility.json'
:cyclonedx | 'gl-sbom.cdx.zip'
:annotations | 'gl-annotations.json'
+ :repository_xray | 'gl-repository-xray.json'
end
with_them do
diff --git a/spec/lib/gitlab/ci/config/entry/retry_spec.rb b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
index 84ef5344a8b..e01b50c5fbd 100644
--- a/spec/lib/gitlab/ci/config/entry/retry_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
@@ -11,8 +11,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Retry do
end
shared_context 'when retry value is a hash', :hash do
- let(:config) { { max: max, when: public_send(:when) }.compact }
+ let(:config) { { max: max, when: public_send(:when), exit_codes: public_send(:exit_codes) }.compact }
let(:when) {}
+ let(:exit_codes) {}
let(:max) {}
end
@@ -43,6 +44,44 @@ RSpec.describe Gitlab::Ci::Config::Entry::Retry do
expect(value).to eq(when: %w[unknown_failure runner_system_failure])
end
end
+
+ context 'and `exit_codes` is an integer' do
+ let(:exit_codes) { 255 }
+
+ it 'returns an array of exit_codes' do
+ expect(value).to eq(exit_codes: [255])
+ end
+ end
+
+ context 'and `exit_codes` is an array' do
+ let(:exit_codes) { [255, 142] }
+
+ it 'returns an array of exit_codes' do
+ expect(value).to eq(exit_codes: [255, 142])
+ end
+ end
+ end
+
+ context 'when ci_retry_on_exit_codes feature flag is disabled', :hash do
+ before do
+ stub_feature_flags(ci_retry_on_exit_codes: false)
+ end
+
+ context 'when `exit_codes` is an integer' do
+ let(:exit_codes) { 255 }
+
+ it 'deletes the attribute exit_codes' do
+ expect(value).to eq({})
+ end
+ end
+
+ context 'when `exit_codes` is an array' do
+ let(:exit_codes) { [255, 137] }
+
+ it 'deletes the attribute exit_codes' do
+ expect(value).to eq({})
+ end
+ end
end
end
@@ -65,6 +104,22 @@ RSpec.describe Gitlab::Ci::Config::Entry::Retry do
end
end
+ context 'with numeric exit_codes' do
+ let(:exit_codes) { 255 }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'with hash values exit_codes' do
+ let(:exit_codes) { [255, 142] }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
context 'with string when' do
let(:when) { 'unknown_failure' }
@@ -202,7 +257,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Retry do
end
end
- context 'iwth max too high' do
+ context 'with max too high' do
let(:max) { 10 }
it 'returns error about value too high' do
@@ -211,6 +266,33 @@ RSpec.describe Gitlab::Ci::Config::Entry::Retry do
end
end
+ context 'with exit_codes in wrong format' do
+ let(:exit_codes) { true }
+
+ it 'raises an error' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'retry exit codes should be an array of integers or an integer'
+ end
+ end
+
+ context 'with exit_codes in wrong array format' do
+ let(:exit_codes) { ['string 1', 'string 2'] }
+
+ it 'raises an error' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'retry exit codes should be an array of integers or an integer'
+ end
+ end
+
+ context 'with exit_codes in wrong mixed array format' do
+ let(:exit_codes) { [255, '155'] }
+
+ it 'raises an error' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'retry exit codes should be an array of integers or an integer'
+ end
+ end
+
context 'with when in wrong format' do
let(:when) { true }
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 1f935bebed5..82747e7b521 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -47,11 +47,23 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
expect(entry.ports).to be_nil
end
end
+
+ describe '#executor_opts' do
+ it "returns service's executor_opts configuration" do
+ expect(entry.executor_opts).to be_nil
+ end
+ end
end
context 'when configuration is a hash' do
let(:config) do
- { name: 'postgresql:9.5', alias: 'db', command: %w[cmd run], entrypoint: %w[/bin/sh run] }
+ {
+ name: 'postgresql:9.5',
+ alias: 'db',
+ command: %w[cmd run],
+ entrypoint: %w[/bin/sh run],
+ variables: { 'MY_VAR' => 'variable' }
+ }
end
describe '#valid?' do
@@ -141,6 +153,51 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
end
end
+ context 'when configuration has docker options' do
+ let(:config) { { name: 'postgresql:9.5', docker: { platform: 'amd64' } } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it "returns value" do
+ expect(entry.value).to eq(
+ name: 'postgresql:9.5',
+ executor_opts: {
+ docker: { platform: 'amd64' }
+ }
+ )
+ end
+ end
+ end
+
+ context 'when docker options have an invalid property' do
+ let(:config) { { name: 'postgresql:9.5', docker: { invalid: 'option' } } }
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors.first)
+ .to match %r{service executor opts '/docker/invalid' must be a valid 'schema'}
+ end
+ end
+ end
+
+ context 'when docker options platform is not string' do
+ let(:config) { { name: 'postgresql:9.5', docker: { platform: 123 } } }
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors.first)
+ .to match %r{service executor opts '/docker/platform' must be a valid 'string'}
+ end
+ end
+ end
+
context 'when configuration has pull_policy' do
let(:config) { { name: 'postgresql:9.5', pull_policy: 'if-not-present' } }
diff --git a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
index 97ac199f47d..d3ce3ffe641 100644
--- a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb
@@ -2,13 +2,12 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::Entry::Workflow do
- let(:factory) { Gitlab::Config::Entry::Factory.new(described_class).value(rules_hash) }
- let(:config) { factory.create! }
+RSpec.describe Gitlab::Ci::Config::Entry::Workflow, feature_category: :pipeline_composition do
+ subject(:config) { described_class.new(workflow_hash) }
describe 'validations' do
context 'when work config value is a string' do
- let(:rules_hash) { 'build' }
+ let(:workflow_hash) { 'build' }
describe '#valid?' do
it 'is invalid' do
@@ -22,13 +21,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Workflow do
describe '#value' do
it 'returns the invalid configuration' do
- expect(config.value).to eq(rules_hash)
+ expect(config.value).to eq(workflow_hash)
end
end
end
context 'when work config value is a hash' do
- let(:rules_hash) { { rules: [{ if: '$VAR' }] } }
+ let(:workflow_hash) { { rules: [{ if: '$VAR' }] } }
describe '#valid?' do
it 'is valid' do
@@ -42,12 +41,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Workflow do
describe '#value' do
it 'returns the config' do
- expect(config.value).to eq(rules_hash)
+ expect(config.value).to eq(workflow_hash)
end
end
context 'with an invalid key' do
- let(:rules_hash) { { trash: [{ if: '$VAR' }] } }
+ let(:workflow_hash) { { trash: [{ if: '$VAR' }] } }
describe '#valid?' do
it 'is invalid' do
@@ -61,64 +60,79 @@ RSpec.describe Gitlab::Ci::Config::Entry::Workflow do
describe '#value' do
it 'returns the invalid configuration' do
- expect(config.value).to eq(rules_hash)
+ expect(config.value).to eq(workflow_hash)
end
end
end
+ end
+ end
- context 'with workflow name' do
- let(:factory) { Gitlab::Config::Entry::Factory.new(described_class).value(workflow_hash) }
+ describe '.default' do
+ it 'is nil' do
+ expect(described_class.default).to be_nil
+ end
+ end
- context 'with a blank name' do
- let(:workflow_hash) do
- { name: '' }
- end
+ context 'with workflow name' do
+ context 'with a blank name' do
+ let(:workflow_hash) do
+ { name: '' }
+ end
- it 'is invalid' do
- expect(config).not_to be_valid
- end
+ it 'is invalid' do
+ expect(config).not_to be_valid
+ end
- it 'returns error about invalid name' do
- expect(config.errors).to include('workflow name is too short (minimum is 1 character)')
- end
- end
+ it 'returns error about invalid name' do
+ expect(config.errors).to include('workflow name is too short (minimum is 1 character)')
+ end
+ end
- context 'with too long name' do
- let(:workflow_hash) do
- { name: 'a' * 256 }
- end
+ context 'with too long name' do
+ let(:workflow_hash) do
+ { name: 'a' * 256 }
+ end
- it 'is invalid' do
- expect(config).not_to be_valid
- end
+ it 'is invalid' do
+ expect(config).not_to be_valid
+ end
- it 'returns error about invalid name' do
- expect(config.errors).to include('workflow name is too long (maximum is 255 characters)')
- end
- end
+ it 'returns error about invalid name' do
+ expect(config.errors).to include('workflow name is too long (maximum is 255 characters)')
+ end
+ end
- context 'when name is nil' do
- let(:workflow_hash) { { name: nil } }
+ context 'when name is nil' do
+ let(:workflow_hash) { { name: nil } }
- it 'is valid' do
- expect(config).to be_valid
- end
- end
+ it 'is valid' do
+ expect(config).to be_valid
+ end
+ end
- context 'when name is not provided' do
- let(:workflow_hash) { { rules: [{ if: '$VAR' }] } }
+ context 'when name is not provided' do
+ let(:workflow_hash) { { rules: [{ if: '$VAR' }] } }
- it 'is valid' do
- expect(config).to be_valid
- end
- end
+ it 'is valid' do
+ expect(config).to be_valid
end
end
end
- describe '.default' do
- it 'is nil' do
- expect(described_class.default).to be_nil
+ context 'with auto_cancel' do
+ let(:workflow_hash) do
+ {
+ auto_cancel: {
+ on_new_commit: 'interruptible',
+ on_job_failure: 'none'
+ }
+ }
+ end
+
+ it { is_expected.to be_valid }
+
+ it 'returns value correctly' do
+ expect(config.value).to eq(workflow_hash)
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index 0643bf0c046..b961ee0d190 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -269,8 +269,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
context_sha: sha,
type: :local,
location: 'lib/gitlab/ci/templates/existent-file.yml',
- blob: "http://localhost/#{project.full_path}/-/blob/#{sha}/lib/gitlab/ci/templates/existent-file.yml",
- raw: "http://localhost/#{project.full_path}/-/raw/#{sha}/lib/gitlab/ci/templates/existent-file.yml",
+ blob: "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/blob/#{sha}/lib/gitlab/ci/templates/existent-file.yml",
+ raw: "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/-/raw/#{sha}/lib/gitlab/ci/templates/existent-file.yml",
extra: {}
)
}
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 f8d3d1019f5..7293e640112 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -75,7 +75,9 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
context 'with a timeout' do
before do
- allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error)
+ allow_next_instance_of(HTTParty::Request) do |instance|
+ allow(instance).to receive(:perform).and_raise(Timeout::Error)
+ end
end
it { is_expected.to be_falsy }
@@ -94,24 +96,33 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
end
end
- describe "#content" do
+ # When the FF ci_parallel_remote_includes is removed,
+ # convert this `shared_context` to `describe` and remove `rubocop:disable`.
+ shared_context "#content" do # rubocop:disable RSpec/ContextWording -- This is temporary until the FF is removed.
+ subject(:content) do
+ remote_file.preload_content
+ remote_file.content
+ end
+
context 'with a valid remote file' do
before do
stub_full_request(location).to_return(body: remote_file_content)
end
it 'returns the content of the file' do
- expect(remote_file.content).to eql(remote_file_content)
+ expect(content).to eql(remote_file_content)
end
end
context 'with a timeout' do
before do
- allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error)
+ allow_next_instance_of(HTTParty::Request) do |instance|
+ allow(instance).to receive(:perform).and_raise(Timeout::Error)
+ end
end
it 'is falsy' do
- expect(remote_file.content).to be_falsy
+ expect(content).to be_falsy
end
end
@@ -123,7 +134,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
end
it 'is nil' do
- expect(remote_file.content).to be_nil
+ expect(content).to be_nil
end
end
@@ -131,11 +142,21 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
let(:location) { 'http://localhost:8080' }
it 'is nil' do
- expect(remote_file.content).to be_nil
+ expect(content).to be_nil
end
end
end
+ it_behaves_like "#content"
+
+ context 'when the FF ci_parallel_remote_includes is disabled' do
+ before do
+ stub_feature_flags(ci_parallel_remote_includes: false)
+ end
+
+ it_behaves_like "#content"
+ end
+
describe "#error_message" do
subject(:error_message) do
Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([remote_file])
@@ -234,13 +255,18 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
end
describe '#to_hash' do
+ subject(:to_hash) do
+ remote_file.preload_content
+ remote_file.to_hash
+ end
+
before do
stub_full_request(location).to_return(body: remote_file_content)
end
context 'with a valid remote file' do
it 'returns the content as a hash' do
- expect(remote_file.to_hash).to eql(
+ expect(to_hash).to eql(
before_script: ["apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs",
"ruby -v",
"which ruby",
@@ -260,7 +286,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
end
it 'returns the content as a hash' do
- expect(remote_file.to_hash).to eql(
+ expect(to_hash).to eql(
include: [
{ local: 'another-file.yml',
rules: [{ exists: ['Dockerfile'] }] }
@@ -293,7 +319,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
it 'returns the content as a hash' do
expect(remote_file).to be_valid
- expect(remote_file.to_hash).to eql(
+ expect(to_hash).to eql(
include: [
{ local: 'some-file.yml',
rules: [{ exists: ['Dockerfile'] }] }
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 5f28b45496f..d67b0ff8895 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -85,7 +85,13 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper, feature_category: :pipeline
an_instance_of(Gitlab::Ci::Config::External::File::Remote))
end
- it_behaves_like 'logging config file fetch', 'config_file_fetch_remote_content_duration_s', 1
+ context 'when the FF ci_parallel_remote_includes is disabled' do
+ before do
+ stub_feature_flags(ci_parallel_remote_includes: false)
+ end
+
+ it_behaves_like 'logging config file fetch', 'config_file_fetch_remote_content_duration_s', 1
+ end
end
context 'when the key is a remote file hash' do
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 68cdf56f198..4684495fa26 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -410,7 +410,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
let(:other_project_files) do
{
- '/component-x/template.yml' => <<~YAML
+ '/templates/component-x/template.yml' => <<~YAML
component_x_job:
script: echo Component X
YAML
diff --git a/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb b/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
index 30036ee68ed..b0a514cb1e2 100644
--- a/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
+++ b/spec/lib/gitlab/ci/config/interpolation/inputs/base_input_spec.rb
@@ -4,8 +4,34 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Interpolation::Inputs::BaseInput, feature_category: :pipeline_composition do
describe '.matches?' do
- it 'is not implemented' do
- expect { described_class.matches?(double) }.to raise_error(NotImplementedError)
+ context 'when given is a hash' do
+ before do
+ stub_const('TestInput', Class.new(described_class))
+
+ TestInput.class_eval do
+ def self.type_name
+ 'test'
+ end
+ end
+ end
+
+ context 'when the spec type matches the input type' do
+ it 'returns true' do
+ expect(TestInput.matches?({ type: 'test' })).to be_truthy
+ end
+ end
+
+ context 'when the spec type does not match the input type' do
+ it 'returns false' do
+ expect(TestInput.matches?({ type: 'string' })).to be_falsey
+ end
+ end
+ end
+
+ context 'when not given a hash' do
+ it 'returns false' do
+ expect(described_class.matches?([])).to be_falsey
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/interpolation/text_interpolator_spec.rb b/spec/lib/gitlab/ci/config/interpolation/text_interpolator_spec.rb
new file mode 100644
index 00000000000..70858c0fff8
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/text_interpolator_spec.rb
@@ -0,0 +1,221 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::TextInterpolator, feature_category: :pipeline_composition do
+ let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(config: [header, content]) }
+
+ subject(:interpolator) { described_class.new(result, arguments, []) }
+
+ context 'when input data is valid' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'correctly interpolates the config' do
+ interpolator.interpolate!
+
+ expect(interpolator).to be_interpolated
+ expect(interpolator).to be_valid
+ expect(interpolator.to_result).to eq("test: 'deploy gitlab.com'")
+ end
+ end
+
+ context 'when config has a syntax error' do
+ let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(error: 'Invalid configuration format') }
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'surfaces an error about invalid config' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('Invalid configuration format')
+ end
+ end
+
+ context 'when spec header is missing but inputs are specified' do
+ let(:header) { nil }
+ let(:content) { "test: 'echo'" }
+ let(:arguments) { { foo: 'bar' } }
+
+ it 'surfaces an error about invalid inputs' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq(
+ 'Given inputs not defined in the `spec` section of the included configuration file'
+ )
+ end
+ end
+
+ context 'when spec header is invalid' do
+ let(:header) do
+ { spec: { arguments: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'surfaces an error about invalid header' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('header:spec config contains unknown keys: arguments')
+ end
+ end
+
+ context 'when provided interpolation argument is invalid' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]]'"
+ end
+
+ let(:arguments) do
+ { website: ['gitlab.com'] }
+ end
+
+ it 'returns an error about the invalid argument' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('`website` input: provided value is not a string')
+ end
+ end
+
+ context 'when interpolation block is invalid' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.abc ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'returns an error about the invalid block' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('unknown interpolation key: `abc`')
+ end
+ end
+
+ context 'when multiple interpolation blocks are invalid' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.something.abc ]] $[[ inputs.cde ]] $[[ efg ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'stops execution after the first invalid block' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq('unknown interpolation key: `something`')
+ end
+ end
+
+ context 'when there are many invalid arguments' do
+ let(:header) do
+ { spec: { inputs: {
+ allow_failure: { type: 'boolean' },
+ image: nil,
+ parallel: { type: 'number' },
+ website: nil
+ } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]] $[[ inputs.parallel ]] $[[ inputs.allow_failure ]] $[[ inputs.image ]]'"
+ end
+
+ let(:arguments) do
+ { allow_failure: 'no', parallel: 'yes', website: 8 }
+ end
+
+ it 'reports a maximum of 3 errors in the error message' do
+ interpolator.interpolate!
+
+ expect(interpolator).not_to be_valid
+ expect(interpolator.error_message).to eq(
+ '`allow_failure` input: provided value is not a boolean, ' \
+ '`image` input: required value has not been provided, ' \
+ '`parallel` input: provided value is not a number'
+ )
+ expect(interpolator.errors).to contain_exactly(
+ '`allow_failure` input: provided value is not a boolean',
+ '`image` input: required value has not been provided',
+ '`parallel` input: provided value is not a number',
+ '`website` input: provided value is not a string'
+ )
+ end
+ end
+
+ describe '#to_result' do
+ context 'when interpolation is not used' do
+ let(:result) do
+ ::Gitlab::Ci::Config::Yaml::Result.new(config: content)
+ end
+
+ let(:content) do
+ "test: 'deploy production'"
+ end
+
+ let(:arguments) { nil }
+
+ it 'returns original content' do
+ interpolator.interpolate!
+
+ expect(interpolator.to_result).to eq(content)
+ end
+ end
+
+ context 'when interpolation is available' do
+ let(:header) do
+ { spec: { inputs: { website: nil } } }
+ end
+
+ let(:content) do
+ "test: 'deploy $[[ inputs.website ]]'"
+ end
+
+ let(:arguments) do
+ { website: 'gitlab.com' }
+ end
+
+ it 'correctly interpolates content' do
+ interpolator.interpolate!
+
+ expect(interpolator.to_result).to eq("test: 'deploy gitlab.com'")
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/interpolation/text_template_spec.rb b/spec/lib/gitlab/ci/config/interpolation/text_template_spec.rb
new file mode 100644
index 00000000000..a2f98fc0d5d
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/interpolation/text_template_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Interpolation::TextTemplate, feature_category: :pipeline_composition do
+ subject(:template) { described_class.new(config, ctx) }
+
+ let(:config) do
+ <<~CFG
+ test:
+ spec:
+ env: $[[ inputs.env ]]
+
+ $[[ inputs.key ]]:
+ name: $[[ inputs.key ]]
+ script: my-value
+ parallel: $[[ inputs.parallel ]]
+ CFG
+ end
+
+ let(:ctx) do
+ { inputs: { env: 'dev', key: 'abc', parallel: 6 } }
+ end
+
+ it 'interpolates the values properly' do
+ expect(template.interpolated).to eq <<~RESULT
+ test:
+ spec:
+ env: dev
+
+ abc:
+ name: abc
+ script: my-value
+ parallel: 6
+ RESULT
+ end
+
+ context 'when the config has an unknown interpolation key' do
+ let(:config) { '$[[ xxx.yyy ]]: abc' }
+
+ it 'does not interpolate the config' do
+ expect(template).not_to be_valid
+ expect(template.interpolated).to be_nil
+ expect(template.errors).to contain_exactly('unknown interpolation key: `xxx`')
+ end
+ end
+
+ context 'when template consists of nested arrays with hashes and values' do
+ let(:config) do
+ <<~CFG
+ test:
+ - a-$[[ inputs.key ]]-b
+ - c-$[[ inputs.key ]]-d:
+ d-$[[ inputs.key ]]-e
+ val: 1
+ CFG
+ end
+
+ it 'performs a valid interpolation' do
+ result = <<~RESULT
+ test:
+ - a-abc-b
+ - c-abc-d:
+ d-abc-e
+ val: 1
+ RESULT
+
+ expect(template).to be_valid
+ expect(template.interpolated).to eq result
+ end
+ end
+
+ context 'when template contains symbols that need interpolation' do
+ subject(:template) do
+ described_class.new("'$[[ inputs.key ]]': 'cde'", ctx)
+ end
+
+ it 'performs a valid interpolation' do
+ expect(template).to be_valid
+ expect(template.interpolated).to eq("'abc': 'cde'")
+ end
+ end
+
+ context 'when template is too large' do
+ before do
+ stub_application_setting(ci_max_total_yaml_size_bytes: 1)
+ end
+
+ it 'returns an error' do
+ expect(template.interpolated).to be_nil
+ expect(template.errors).to contain_exactly('config too large')
+ end
+ end
+
+ context 'when there are too many interpolation blocks' do
+ before do
+ stub_const("#{described_class}::MAX_BLOCKS", 1)
+ end
+
+ it 'returns an error' do
+ expect(template.interpolated).to be_nil
+ expect(template.errors).to contain_exactly('too many interpolation blocks')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index fdf152b3584..76be65d91c4 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -43,6 +43,34 @@ RSpec.describe Gitlab::Ci::Config, feature_category: :pipeline_composition do
expect(config.to_hash).to eq hash
end
+ context 'when yml has stages' do
+ let(:yml) do
+ <<-EOS
+ image: image:1.0
+ stages:
+ - custom_stage
+ rspec:
+ script:
+ - gem install rspec
+ - rspec
+ EOS
+ end
+
+ specify do
+ expect(config.to_hash[:stages]).to eq(['.pre', 'custom_stage', '.post'])
+ end
+
+ context 'with inject_edge_stages option disabled' do
+ let(:config) do
+ described_class.new(yml, project: nil, pipeline: nil, sha: nil, user: nil, inject_edge_stages: false)
+ end
+
+ specify do
+ expect(config.to_hash[:stages]).to contain_exactly('custom_stage')
+ end
+ end
+ end
+
describe '#valid?' do
it 'is valid' do
expect(config).to be_valid
diff --git a/spec/lib/gitlab/ci/jwt_v2_spec.rb b/spec/lib/gitlab/ci/jwt_v2_spec.rb
index c2ced10620b..1093e6331cd 100644
--- a/spec/lib/gitlab/ci/jwt_v2_spec.rb
+++ b/spec/lib/gitlab/ci/jwt_v2_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::JwtV2, feature_category: :continuous_integration do
+RSpec.describe Gitlab::Ci::JwtV2, feature_category: :secrets_management do
let(:namespace) { build_stubbed(:namespace) }
let(:project) { build_stubbed(:project, namespace: namespace) }
let(:user) do
diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb
index a331af9a9ac..9c8402faf77 100644
--- a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb
@@ -33,35 +33,27 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx, feature_category: :dependen
allow(SecureRandom).to receive(:uuid).and_return(uuid)
end
- context 'when report JSON is invalid' do
- let(:raw_report_data) { '{ ' }
+ context 'when report is invalid' do
+ context 'when report JSON is invalid' do
+ let(:raw_report_data) { '{ ' }
- it 'handles errors and adds them to the report' do
- expect(report).to receive(:add_error).with(a_string_including("Report JSON is invalid:"))
+ it 'handles errors and adds them to the report' do
+ expect(report).to receive(:add_error).with(a_string_including("Report JSON is invalid:"))
- expect { parse! }.not_to raise_error
+ expect { parse! }.not_to raise_error
+ end
end
- end
-
- context 'when report uses an unsupported spec version' do
- let(:report_data) { base_report_data.merge({ 'specVersion' => '1.3' }) }
-
- it 'reports unsupported version as an error' do
- expect(report).to receive(:add_error).with("Unsupported CycloneDX spec version. Must be one of: 1.4")
- parse!
- end
- end
+ context 'when report does not conform to the CycloneDX schema' do
+ let(:report_valid?) { false }
+ let(:validator_errors) { %w[error1 error2] }
- context 'when report does not conform to the CycloneDX schema' do
- let(:report_valid?) { false }
- let(:validator_errors) { %w[error1 error2] }
+ it 'reports all errors returned by the validator' do
+ expect(report).to receive(:add_error).with("error1")
+ expect(report).to receive(:add_error).with("error2")
- it 'reports all errors returned by the validator' do
- expect(report).to receive(:add_error).with("error1")
- expect(report).to receive(:add_error).with("error2")
-
- parse!
+ parse!
+ end
end
end
@@ -109,25 +101,26 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx, feature_category: :dependen
it 'adds each component, ignoring unused attributes' do
expect(report).to receive(:add_component)
- .with(
- an_object_having_attributes(
- name: "activesupport",
- version: "5.1.4",
- component_type: "library",
- purl: an_object_having_attributes(type: "gem")
- )
- )
+ .with(
+ an_object_having_attributes(
+ name: "activesupport",
+ version: "5.1.4",
+ component_type: "library",
+ purl: an_object_having_attributes(type: "gem")
+ )
+ )
expect(report).to receive(:add_component)
- .with(
- an_object_having_attributes(
- name: "byebug",
- version: "10.0.0",
- component_type: "library",
- purl: an_object_having_attributes(type: "gem")
- )
- )
+ .with(
+ an_object_having_attributes(
+ name: "byebug",
+ version: "10.0.0",
+ component_type: "library",
+ purl: an_object_having_attributes(type: "gem")
+ )
+ )
expect(report).to receive(:add_component)
- .with(an_object_having_attributes(name: "minimal-component", version: nil, component_type: "library"))
+ .with(an_object_having_attributes(name: "minimal-component", version: nil,
+ component_type: "library"))
parse!
end
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 acb7c122bcd..9422290761d 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
@@ -4,160 +4,116 @@ require "spec_helper"
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/
-
- subject(:validator) { described_class.new(report_data) }
-
- let_it_be(:required_attributes) do
+ let(:report_data) do
{
"bomFormat" => "CycloneDX",
- "specVersion" => "1.4",
+ "specVersion" => spec_version,
"version" => 1
}
end
- context "with minimally valid report" do
- let_it_be(:report_data) { required_attributes }
-
- it { is_expected.to be_valid }
- end
-
- context "when report has components" do
- let(:report_data) { required_attributes.merge({ "components" => components }) }
-
- context "with minimally valid components" do
- let(:components) do
- [
- {
- "type" => "library",
- "name" => "activesupport"
- },
- {
- "type" => "library",
- "name" => "byebug"
- }
- ]
- end
+ subject(:validator) { described_class.new(report_data) }
- it { is_expected.to be_valid }
+ shared_examples 'a validator that performs the expected validations' do
+ let(:required_attributes) do
+ {
+ "bomFormat" => "CycloneDX",
+ "specVersion" => spec_version,
+ "version" => 1
+ }
end
- context "when components have versions" do
- let(:components) do
- [
- {
- "type" => "library",
- "name" => "activesupport",
- "version" => "5.1.4"
- },
- {
- "type" => "library",
- "name" => "byebug",
- "version" => "10.0.0"
- }
- ]
- end
+ context "with minimally valid report" do
+ let(:report_data) { required_attributes }
it { is_expected.to be_valid }
end
- context 'when components have licenses' do
- let(:components) do
- [
- {
- "type" => "library",
- "name" => "activesupport",
- "version" => "5.1.4",
- "licenses" => [
- { "license" => { "id" => "MIT" } }
- ]
- }
- ]
- end
+ context "when report has components" do
+ let(:report_data) { required_attributes.merge({ "components" => components }) }
- it { is_expected.to be_valid }
- end
-
- context 'when components have a signature' do
- let(:components) do
- [
- {
- "type" => "library",
- "name" => "activesupport",
- "version" => "5.1.4",
- "signature" => {
- "algorithm" => "ES256",
- "publicKey" => {
- "kty" => "EC",
- "crv" => "P-256",
- "x" => "6BKxpty8cI-exDzCkh-goU6dXq3MbcY0cd1LaAxiNrU",
- "y" => "mCbcvUzm44j3Lt2b5BPyQloQ91tf2D2V-gzeUxWaUdg"
- },
- "value" => "ybT1qz5zHNi4Ndc6y7Zhamuf51IqXkPkZwjH1XcC-KSuBiaQplTw6Jasf2MbCLg3CF7PAdnMO__WSLwvI5r2jA"
+ context "with minimally valid components" do
+ let(:components) do
+ [
+ {
+ "type" => "library",
+ "name" => "activesupport"
+ },
+ {
+ "type" => "library",
+ "name" => "byebug"
}
- }
- ]
- end
-
- it { is_expected.to be_valid }
- end
+ ]
+ end
- context "when components are not valid" do
- let(:components) do
- [
- { "type" => "foo" },
- { "name" => "activesupport" }
- ]
+ it { is_expected.to be_valid }
end
- it { is_expected.not_to be_valid }
-
- it "outputs errors for each validation failure" do
- expect(validator.errors).to match_array(
+ context "when components have versions" do
+ let(:components) do
[
- "property '/components/0' is missing required keys: name",
- "property '/components/0/type' is not one of: [\"application\", \"framework\"," \
- " \"library\", \"container\", \"operating-system\", \"device\", \"firmware\", \"file\"]",
- "property '/components/1' is missing required keys: type"
- ])
- end
- end
- end
-
- context "when report has metadata" do
- let(:metadata) do
- {
- "timestamp" => "2022-02-23T08:02:39Z",
- "tools" => [{ "vendor" => "GitLab", "name" => "Gemnasium", "version" => "2.34.0" }],
- "authors" => [{ "name" => "GitLab", "email" => "support@gitlab.com" }]
- }
- end
+ {
+ "type" => "library",
+ "name" => "activesupport",
+ "version" => "5.1.4"
+ },
+ {
+ "type" => "library",
+ "name" => "byebug",
+ "version" => "10.0.0"
+ }
+ ]
+ end
- let(:report_data) { required_attributes.merge({ "metadata" => metadata }) }
+ it { is_expected.to be_valid }
+ end
- it { is_expected.to be_valid }
+ context 'when components have licenses' do
+ let(:components) do
+ [
+ {
+ "type" => "library",
+ "name" => "activesupport",
+ "version" => "5.1.4",
+ "licenses" => [
+ { "license" => { "id" => "MIT" } }
+ ]
+ }
+ ]
+ end
- context "when metadata has properties" do
- before do
- metadata.merge!({ "properties" => properties })
+ it { is_expected.to be_valid }
end
- context "when properties are valid" do
- let(:properties) do
+ context 'when components have a signature' do
+ let(:components) do
[
- { "name" => "gitlab:dependency_scanning:input_file", "value" => "Gemfile.lock" },
- { "name" => "gitlab:dependency_scanning:package_manager", "value" => "bundler" }
+ {
+ "type" => "library",
+ "name" => "activesupport",
+ "version" => "5.1.4",
+ "signature" => {
+ "algorithm" => "ES256",
+ "publicKey" => {
+ "kty" => "EC",
+ "crv" => "P-256",
+ "x" => "6BKxpty8cI-exDzCkh-goU6dXq3MbcY0cd1LaAxiNrU",
+ "y" => "mCbcvUzm44j3Lt2b5BPyQloQ91tf2D2V-gzeUxWaUdg"
+ },
+ "value" => "ybT1qz5zHNi4Ndc6y7Zhamuf51IqXkPkZwjH1XcC-KSuBiaQplTw6Jasf2MbCLg3CF7PAdnMO__WSLwvI5r2jA"
+ }
+ }
]
end
it { is_expected.to be_valid }
end
- context "when properties are invalid" do
- let(:properties) do
+ context "when components are not valid" do
+ let(:components) do
[
- { "name" => ["gitlab:meta:schema_version"], "value" => 1 }
+ { "type" => "foo" },
+ { "name" => "activesupport" }
]
end
@@ -166,11 +122,75 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator,
it "outputs errors for each validation failure" do
expect(validator.errors).to match_array(
[
- "property '/metadata/properties/0/name' is not of type: string",
- "property '/metadata/properties/0/value' is not of type: string"
+ "property '/components/0' is missing required keys: name",
+ a_string_starting_with("property '/components/0/type' is not one of:"),
+ "property '/components/1' is missing required keys: type"
])
end
end
end
+
+ context "when report has metadata" do
+ let(:metadata) do
+ {
+ "timestamp" => "2022-02-23T08:02:39Z",
+ "tools" => [{ "vendor" => "GitLab", "name" => "Gemnasium", "version" => "2.34.0" }],
+ "authors" => [{ "name" => "GitLab", "email" => "support@gitlab.com" }]
+ }
+ end
+
+ let(:report_data) { required_attributes.merge({ "metadata" => metadata }) }
+
+ it { is_expected.to be_valid }
+
+ context "when metadata has properties" do
+ before do
+ metadata.merge!({ "properties" => properties })
+ end
+
+ context "when properties are valid" do
+ let(:properties) do
+ [
+ { "name" => "gitlab:dependency_scanning:input_file", "value" => "Gemfile.lock" },
+ { "name" => "gitlab:dependency_scanning:package_manager", "value" => "bundler" }
+ ]
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when properties are invalid" do
+ let(:properties) do
+ [
+ { "name" => ["gitlab:meta:schema_version"], "value" => 1 }
+ ]
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it "outputs errors for each validation failure" do
+ expect(validator.errors).to match_array(
+ [
+ "property '/metadata/properties/0/name' is not of type: string",
+ "property '/metadata/properties/0/value' is not of type: string"
+ ])
+ end
+ end
+ end
+ end
+ end
+
+ context 'when spec version is supported' do
+ where(:spec_version) { %w[1.4 1.5] }
+
+ with_them do
+ it_behaves_like 'a validator that performs the expected validations'
+ end
+ end
+
+ context 'when spec version is not supported' do
+ let(:spec_version) { '1.3' }
+
+ it { is_expected.not_to be_valid }
end
end
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 31bffcbeb2a..00f834fcf80 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
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines, feature_category: :continuous_integration do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
- let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:command) { Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) }
let_it_be(:step) { described_class.new(pipeline, command) }
@@ -17,5 +17,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines, feature_cate
subject
end
+
+ context 'with scheduled pipelines' do
+ before do
+ pipeline.source = :schedule
+ end
+
+ it 'enqueues LowUrgencyCancelRedundantPipelinesWorker' do
+ expect(Ci::LowUrgencyCancelRedundantPipelinesWorker)
+ .to receive(:perform_async).with(pipeline.id)
+
+ subject
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
index eb5a37f19f4..44ccb1eeae1 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
@@ -12,10 +12,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
end
let(:step) { described_class.new(pipeline, command) }
+ let(:ff_always_set_pipeline_failure_reason) { true }
describe '#perform!' do
context 'when pipeline has been skipped by workflow configuration' do
before do
+ stub_feature_flags(always_set_pipeline_failure_reason: ff_always_set_pipeline_failure_reason)
+
allow(step).to receive(:workflow_rules_result)
.and_return(
double(pass?: false, variables: {})
@@ -39,6 +42,20 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
it 'saves workflow_rules_result' do
expect(command.workflow_rules_result.variables).to eq({})
end
+
+ it 'sets the failure reason', :aggregate_failures do
+ expect(pipeline).to be_failed
+ expect(pipeline).to be_filtered_by_workflow_rules
+ end
+
+ context 'when always_set_pipeline_failure_reason is disabled' do
+ let(:ff_always_set_pipeline_failure_reason) { false }
+
+ it 'does not set the failure reason', :aggregate_failures do
+ expect(pipeline).not_to be_failed
+ expect(pipeline.failure_reason).to be_blank
+ end
+ end
end
context 'when pipeline has not been skipped by workflow configuration' do
@@ -67,6 +84,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
it 'saves workflow_rules_result' do
expect(command.workflow_rules_result.variables).to eq({ 'VAR1' => 'val2', 'VAR2' => 3 })
end
+
+ it 'does not set a failure reason' do
+ expect(pipeline).not_to be_filtered_by_workflow_rules
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb
index 96ada90b4e1..84c2fb6525e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers, feature_category: :continuous_integration do
let(:helper_class) do
Class.new do
include Gitlab::Ci::Pipeline::Chain::Helpers
@@ -38,14 +38,35 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
describe '.error' do
shared_examples 'error function' do
specify do
- expect(pipeline).to receive(:drop!).with(drop_reason).and_call_original
expect(pipeline).to receive(:add_error_message).with(message).and_call_original
- expect(pipeline).to receive(:ensure_project_iid!).twice.and_call_original
+
+ if command.save_incompleted
+ expect(pipeline).to receive(:ensure_project_iid!).twice.and_call_original
+ expect(pipeline).to receive(:drop!).with(drop_reason).and_call_original
+ end
subject.error(message, config_error: config_error, drop_reason: drop_reason)
expect(pipeline.yaml_errors).to eq(yaml_error)
expect(pipeline.errors[:base]).to include(message)
+ expect(pipeline.status).to eq 'failed'
+ expect(pipeline.failure_reason).to eq drop_reason.to_s
+ end
+
+ context 'when feature flag always_set_pipeline_failure_reason is false' do
+ before do
+ stub_feature_flags(always_set_pipeline_failure_reason: false)
+ end
+
+ specify do
+ subject.error(message, config_error: config_error, drop_reason: drop_reason)
+
+ if command.save_incompleted
+ expect(pipeline.failure_reason).to eq drop_reason.to_s
+ else
+ expect(pipeline.failure_reason).not_to be_present
+ end
+ end
end
end
@@ -79,6 +100,43 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
let(:yaml_error) { nil }
it_behaves_like "error function"
+
+ specify do
+ subject.error(message, config_error: config_error, drop_reason: drop_reason)
+
+ expect(pipeline).to be_persisted
+ end
+
+ context ' when the drop reason is not persistable' do
+ let(:drop_reason) { :filtered_by_rules }
+ let(:command) { double(project: nil) }
+
+ specify do
+ expect(command).to receive(:increment_pipeline_failure_reason_counter)
+
+ subject.error(message, config_error: config_error, drop_reason: drop_reason)
+
+ expect(pipeline).to be_failed
+ expect(pipeline.failure_reason).to eq drop_reason.to_s
+ expect(pipeline).not_to be_persisted
+ end
+ end
+
+ context 'when save_incompleted is false' do
+ let(:command) { double(save_incompleted: false, project: nil) }
+
+ before do
+ allow(command).to receive(:increment_pipeline_failure_reason_counter)
+ end
+
+ it_behaves_like "error function"
+
+ specify do
+ subject.error(message, config_error: config_error, drop_reason: drop_reason)
+
+ expect(pipeline).not_to be_persisted
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_metadata_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_metadata_spec.rb
index 00200b57b1e..732748d8c8b 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_metadata_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Chain::PopulateMetadata do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::PopulateMetadata, feature_category: :pipeline_composition do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
@@ -43,16 +43,28 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::PopulateMetadata do
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
- context 'with pipeline name' do
- let(:config) do
- { workflow: { name: ' Pipeline name ' }, rspec: { script: 'rspec' } }
- end
-
+ shared_examples 'not breaking the chain' do
it 'does not break the chain' do
run_chain
expect(step.break?).to be false
end
+ end
+
+ shared_examples 'not saving pipeline metadata' do
+ it 'does not save pipeline metadata' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata).to be_nil
+ end
+ end
+
+ context 'with pipeline name' do
+ let(:config) do
+ { workflow: { name: ' Pipeline name ' }, rspec: { script: 'rspec' } }
+ end
+
+ it_behaves_like 'not breaking the chain'
it 'builds pipeline_metadata' do
run_chain
@@ -67,22 +79,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::PopulateMetadata do
{ workflow: { name: ' ' }, rspec: { script: 'rspec' } }
end
- it 'strips whitespace from name' do
- run_chain
-
- expect(pipeline.pipeline_metadata).to be_nil
- end
+ it_behaves_like 'not saving pipeline metadata'
context 'with empty name after variable substitution' do
let(:config) do
{ workflow: { name: '$VAR1' }, rspec: { script: 'rspec' } }
end
- it 'does not save empty name' do
- run_chain
-
- expect(pipeline.pipeline_metadata).to be_nil
- end
+ it_behaves_like 'not saving pipeline metadata'
end
end
@@ -127,4 +131,140 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::PopulateMetadata do
end
end
end
+
+ context 'with auto_cancel' do
+ let(:on_new_commit) { 'interruptible' }
+ let(:on_job_failure) { 'all' }
+ let(:auto_cancel) { { on_new_commit: on_new_commit, on_job_failure: on_job_failure } }
+ let(:config) { { workflow: { auto_cancel: auto_cancel }, rspec: { script: 'rspec' } } }
+
+ it_behaves_like 'not breaking the chain'
+
+ it 'builds pipeline_metadata' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata.auto_cancel_on_new_commit).to eq('interruptible')
+ expect(pipeline.pipeline_metadata.auto_cancel_on_job_failure).to eq('all')
+ expect(pipeline.pipeline_metadata).not_to be_persisted
+ end
+
+ context 'with no auto_cancel' do
+ let(:config) do
+ { rspec: { script: 'rspec' } }
+ end
+
+ it_behaves_like 'not saving pipeline metadata'
+ end
+
+ context 'with auto_cancel: nil' do
+ let(:auto_cancel) { nil }
+
+ it_behaves_like 'not saving pipeline metadata'
+ end
+
+ context 'with auto_cancel_on_new_commit and no auto_cancel_on_job_failure' do
+ let(:auto_cancel) { { on_new_commit: on_new_commit } }
+
+ it 'builds pipeline_metadata' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata.auto_cancel_on_new_commit).to eq('interruptible')
+ expect(pipeline.pipeline_metadata.auto_cancel_on_job_failure).to eq('none')
+ expect(pipeline.pipeline_metadata).not_to be_persisted
+ end
+ end
+
+ context 'with auto_cancel_on_job_failure and no auto_cancel_on_new_commit' do
+ let(:auto_cancel) { { on_job_failure: on_job_failure } }
+
+ it 'builds pipeline_metadata' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata.auto_cancel_on_new_commit).to eq('conservative')
+ expect(pipeline.pipeline_metadata.auto_cancel_on_job_failure).to eq('all')
+ expect(pipeline.pipeline_metadata).not_to be_persisted
+ end
+ end
+
+ context 'with auto_cancel_on_new_commit: nil and auto_cancel_on_job_failure: nil' do
+ let(:on_new_commit) { nil }
+ let(:on_job_failure) { nil }
+
+ it_behaves_like 'not saving pipeline metadata'
+ end
+
+ context 'with auto_cancel_on_new_commit valid and auto_cancel_on_job_failure: nil' do
+ let(:on_job_failure) { nil }
+
+ it 'builds pipeline_metadata' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata.auto_cancel_on_new_commit).to eq('interruptible')
+ expect(pipeline.pipeline_metadata.auto_cancel_on_job_failure).to eq('none')
+ expect(pipeline.pipeline_metadata).not_to be_persisted
+ end
+ end
+
+ context 'with auto_cancel_on_new_commit: nil and auto_cancel_on_job_failure valid' do
+ let(:on_new_commit) { nil }
+
+ it 'builds pipeline_metadata' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata.auto_cancel_on_new_commit).to eq('conservative')
+ expect(pipeline.pipeline_metadata.auto_cancel_on_job_failure).to eq('all')
+ expect(pipeline.pipeline_metadata).not_to be_persisted
+ end
+ end
+
+ context 'when auto_cancel_on_job_failure: none' do
+ let(:on_job_failure) { 'none' }
+
+ it 'builds pipeline_metadata' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata.auto_cancel_on_job_failure).to eq('none')
+ expect(pipeline.pipeline_metadata).not_to be_persisted
+ end
+ end
+
+ context 'when auto_cancel_pipeline_on_job_failure feature is disabled' do
+ before do
+ stub_feature_flags(auto_cancel_pipeline_on_job_failure: false)
+ end
+
+ it 'ignores the auto_cancel_on_job_failure value' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata.auto_cancel_on_job_failure).to eq('none')
+ expect(pipeline.pipeline_metadata).not_to be_persisted
+ end
+ end
+ end
+
+ context 'with both pipeline name and auto_cancel' do
+ let(:config) do
+ {
+ workflow: {
+ name: 'Pipeline name',
+ auto_cancel: {
+ on_new_commit: 'interruptible',
+ on_job_failure: 'none'
+ }
+ },
+ rspec: { script: 'rspec' }
+ }
+ end
+
+ it_behaves_like 'not breaking the chain'
+
+ it 'builds pipeline_metadata' do
+ run_chain
+
+ expect(pipeline.pipeline_metadata.name).to eq('Pipeline name')
+ expect(pipeline.pipeline_metadata.auto_cancel_on_new_commit).to eq('interruptible')
+ expect(pipeline.pipeline_metadata.auto_cancel_on_job_failure).to eq('none')
+ expect(pipeline.pipeline_metadata).not_to be_persisted
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 91bb94bbb11..476b1be35a9 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -34,12 +34,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate, feature_category: :continu
{ rspec: { script: 'rspec' } }
end
+ let(:ff_always_set_pipeline_failure_reason) { true }
+
def run_chain
dependencies.map(&:perform!)
step.perform!
end
before do
+ stub_feature_flags(always_set_pipeline_failure_reason: ff_always_set_pipeline_failure_reason)
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
@@ -100,7 +103,27 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate, feature_category: :continu
it 'increments the error metric' do
counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
- expect { run_chain }.to change { counter.get(reason: 'unknown_failure') }.by(1)
+ expect { run_chain }.to change { counter.get(reason: 'filtered_by_rules') }.by(1)
+ end
+
+ it 'sets the failure reason without persisting the pipeline', :aggregate_failures do
+ run_chain
+
+ expect(pipeline).not_to be_persisted
+ expect(pipeline).to be_failed
+ expect(pipeline).to be_filtered_by_rules
+ end
+
+ context 'when ff always_set_pipeline_failure_reason is disabled' do
+ let(:ff_always_set_pipeline_failure_reason) { false }
+
+ it 'sets the failure reason without persisting the pipeline', :aggregate_failures do
+ run_chain
+
+ expect(pipeline).not_to be_persisted
+ expect(pipeline).not_to be_failed
+ expect(pipeline).not_to be_filtered_by_rules
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
index 52a00e0d501..4017076d29f 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External, feature_category: :continuous_integration do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, :with_sign_ins) }
@@ -328,11 +328,12 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
context 'when save_incompleted is false' do
let(:save_incompleted) { false }
- it 'adds errors to the pipeline without dropping it' do
+ it 'adds errors to the pipeline without persisting it', :aggregate_failures do
perform!
- expect(pipeline.status).to eq('pending')
expect(pipeline).not_to be_persisted
+ expect(pipeline.status).to eq('failed')
+ expect(pipeline).to be_external_validation_failure
expect(pipeline.errors.to_a).to include('External validation failed')
end
diff --git a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
index c1eaea511b7..09a601833ad 100644
--- a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
+++ b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
@@ -5,47 +5,93 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Reports::Sbom::Source, feature_category: :dependency_management do
let(:attributes) do
{
- type: :dependency_scanning,
- data: {
- 'category' => 'development',
- 'input_file' => { 'path' => 'package-lock.json' },
- 'source_file' => { 'path' => 'package.json' },
- 'package_manager' => { 'name' => 'npm' },
- 'language' => { 'name' => 'JavaScript' }
- }
+ type: type,
+ data: { 'category' => 'development',
+ 'package_manager' => { 'name' => 'npm' },
+ 'language' => { 'name' => 'JavaScript' } }.merge(extra_attributes)
}
end
- subject { described_class.new(**attributes) }
+ subject(:source) { described_class.new(**attributes) }
- it 'has correct attributes' do
- expect(subject).to have_attributes(
- source_type: attributes[:type],
- data: attributes[:data]
- )
- end
+ shared_examples_for 'it has correct common attributes' do
+ it 'has correct type and data' do
+ expect(subject).to have_attributes(
+ source_type: type,
+ data: attributes[:data]
+ )
+ end
- describe '#source_file_path' do
- it 'returns the correct source_file_path' do
- expect(subject.source_file_path).to eq('package.json')
+ describe '#packager' do
+ it 'returns the correct package manager name' do
+ expect(subject.packager).to eq("npm")
+ end
end
- end
- describe '#input_file_path' do
- it 'returns the correct input_file_path' do
- expect(subject.input_file_path).to eq("package-lock.json")
+ describe '#language' do
+ it 'returns the correct language' do
+ expect(subject.language).to eq("JavaScript")
+ end
end
end
- describe '#packager' do
- it 'returns the correct package manager name' do
- expect(subject.packager).to eq("npm")
+ context 'when dependency scanning' do
+ let(:type) { :dependency_scanning }
+ let(:extra_attributes) do
+ {
+ 'input_file' => { 'path' => 'package-lock.json' },
+ 'source_file' => { 'path' => 'package.json' }
+ }
+ end
+
+ it_behaves_like 'it has correct common attributes'
+
+ describe '#source_file_path' do
+ it 'returns the correct source_file_path' do
+ expect(subject.source_file_path).to eq('package.json')
+ end
+ end
+
+ describe '#input_file_path' do
+ it 'returns the correct input_file_path' do
+ expect(subject.input_file_path).to eq("package-lock.json")
+ end
end
end
- describe '#language' do
- it 'returns the correct langauge' do
- expect(subject.language).to eq("JavaScript")
+ context 'when container scanning' do
+ let(:type) { :container_scanning }
+ let(:extra_attributes) do
+ {
+ "image" => { "name" => "rhel", "tag" => "7.1" },
+ "operating_system" => { "name" => "Red Hat Enterprise Linux", "version" => "7" }
+ }
+ end
+
+ it_behaves_like 'it has correct common attributes'
+
+ describe "#image_name" do
+ subject { source.image_name }
+
+ it { is_expected.to eq("rhel") }
+ end
+
+ describe "#image_tag" do
+ subject { source.image_tag }
+
+ it { is_expected.to eq("7.1") }
+ end
+
+ describe "#operating_system_name" do
+ subject { source.operating_system_name }
+
+ it { is_expected.to eq("Red Hat Enterprise Linux") }
+ end
+
+ describe "#operating_system_version" do
+ subject { source.operating_system_version }
+
+ it { is_expected.to eq("7") }
end
end
end
diff --git a/spec/lib/gitlab/ci/runner_instructions_spec.rb b/spec/lib/gitlab/ci/runner_instructions_spec.rb
index 31c53d4a030..6da649393f3 100644
--- a/spec/lib/gitlab/ci/runner_instructions_spec.rb
+++ b/spec/lib/gitlab/ci/runner_instructions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::RunnerInstructions, feature_category: :runner_fleet do
+RSpec.describe Gitlab::Ci::RunnerInstructions, feature_category: :fleet_visibility do
using RSpec::Parameterized::TableSyntax
let(:params) { {} }
diff --git a/spec/lib/gitlab/ci/runner_releases_spec.rb b/spec/lib/gitlab/ci/runner_releases_spec.rb
index 9e211327dee..126a5b85471 100644
--- a/spec/lib/gitlab/ci/runner_releases_spec.rb
+++ b/spec/lib/gitlab/ci/runner_releases_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::RunnerReleases, feature_category: :runner_fleet do
+RSpec.describe Gitlab::Ci::RunnerReleases, feature_category: :fleet_visibility do
subject { described_class.instance }
let(:runner_releases_url) { 'http://testurl.com/runner_public_releases' }
diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
index 526d6cba657..778c0aa69de 100644
--- a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
+++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::RunnerUpgradeCheck, feature_category: :runner_fleet do
+RSpec.describe Gitlab::Ci::RunnerUpgradeCheck, feature_category: :fleet_visibility do
using RSpec::Parameterized::TableSyntax
subject(:instance) { described_class.new(gitlab_version, runner_releases) }
diff --git a/spec/lib/gitlab/ci/templates/Diffblue_Cover_spec.rb b/spec/lib/gitlab/ci/templates/Diffblue_Cover_spec.rb
new file mode 100644
index 00000000000..c16356bfda7
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/Diffblue_Cover_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Diffblue-Cover.gitlab-ci.yml', feature_category: :continuous_integration do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Diffblue-Cover') }
+
+ describe 'the created pipeline' do
+ let(:pipeline_branch) { 'patch-1' }
+ let_it_be(:project) { create(:project, :repository, create_branch: 'patch-1') }
+ let(:user) { project.first_owner }
+
+ let(:mr_service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project, source_branch: pipeline_branch) }
+ let(:mr_pipeline) { mr_service.execute(merge_request).payload }
+ let(:mr_build_names) { mr_pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ end
+
+ it 'creates diffblue-cover jobs' do
+ expect(mr_build_names).to include('diffblue-cover')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb
index 36c6e805bdf..98f0d32960b 100644
--- a/spec/lib/gitlab/ci/templates/templates_spec.rb
+++ b/spec/lib/gitlab/ci/templates/templates_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe 'CI YML Templates' do
context 'that support autodevops' do
exceptions = [
+ 'Diffblue-Cover.gitlab-ci.yml', # no auto-devops
'Security/DAST.gitlab-ci.yml', # DAST stage is defined inside AutoDevops yml
'Security/DAST-API.gitlab-ci.yml', # no auto-devops
'Security/API-Fuzzing.gitlab-ci.yml', # no auto-devops
diff --git a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
index 860a1fd30bd..f8d67a6f0b4 100644
--- a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
@@ -66,6 +66,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
let_it_be(:assignees) { create_list(:user, 2) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:labels) { create_list(:label, 2) }
+ let(:merge_request_description) { nil }
let(:merge_request) do
create(:merge_request, :simple,
@@ -73,6 +74,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
target_project: project,
assignees: assignees,
milestone: milestone,
+ description: merge_request_description,
labels: labels)
end
@@ -113,6 +115,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
merge_request.source_branch
).to_s,
'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description,
+ 'CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED' => 'false',
'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
@@ -121,6 +125,78 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
'CI_MERGE_REQUEST_SQUASH_ON_MERGE' => merge_request.squash_on_merge?.to_s
end
+ context 'when merge request description hits the limit' do
+ let(:merge_request_description) { 'a' * (MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH + 1) }
+
+ it 'truncates the exposed description' do
+ truncated_description = merge_request.description.truncate(
+ MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH
+ )
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DESCRIPTION' => truncated_description,
+ 'CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED' => 'true'
+ )
+ end
+ end
+
+ context 'when merge request description fits the length limit' do
+ let(:merge_request_description) { 'a' * (MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH - 1) }
+
+ it 'does not truncate the exposed description' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description,
+ 'CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED' => 'false'
+ )
+ end
+ end
+
+ context 'when truncate_ci_merge_request_description feature flag is disabled' do
+ before do
+ stub_feature_flags(truncate_ci_merge_request_description: false)
+ end
+
+ context 'when merge request description hits the limit' do
+ let(:merge_request_description) { 'a' * (MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH + 1) }
+
+ it 'does not truncate the exposed description' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description
+ )
+ expect(subject.to_hash)
+ .not_to have_key('CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED')
+ end
+ end
+
+ context 'when merge request description fits the length limit' do
+ let(:merge_request_description) { 'a' * (MergeRequest::CI_MERGE_REQUEST_DESCRIPTION_MAX_LENGTH - 1) }
+
+ it 'does not truncate the exposed description' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description
+ )
+ expect(subject.to_hash)
+ .not_to have_key('CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED')
+ end
+ end
+
+ context 'when merge request description does not exist' do
+ let(:merge_request_description) { nil }
+
+ it 'does not truncate the exposed description' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description
+ )
+ expect(subject.to_hash)
+ .not_to have_key('CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED')
+ end
+ end
+ end
+
it 'exposes diff variables' do
expect(subject.to_hash)
.to include(
@@ -214,6 +290,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :secr
'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => merge_request.source_branch_sha,
'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_DESCRIPTION' => merge_request.description,
'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
diff --git a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
index cd68b0cdf2b..f5845e492bc 100644
--- a/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
+++ b/spec/lib/gitlab/ci/variables/downstream/generator_spec.rb
@@ -39,6 +39,15 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
]
end
+ let(:pipeline_dotenv_variables) do
+ [
+ { key: 'PIPELINE_DOTENV_VAR1', value: 'variable 1' },
+ { key: 'PIPELINE_DOTENV_VAR2', value: 'variable 2' },
+ { key: 'PIPELINE_DOTENV_RAW_VAR3', value: '$REF1', raw: true },
+ { key: 'PIPELINE_DOTENV_INTERPOLATION_VAR4', value: 'interpolate $REF1 $REF2' }
+ ]
+ end
+
let(:bridge) do
instance_double(
'Ci::Bridge',
@@ -48,7 +57,8 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
expand_file_refs?: false,
yaml_variables: yaml_variables,
pipeline_variables: pipeline_variables,
- pipeline_schedule_variables: pipeline_schedule_variables
+ pipeline_schedule_variables: pipeline_schedule_variables,
+ dependency_variables: pipeline_dotenv_variables
)
end
@@ -69,7 +79,12 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
{ key: 'PIPELINE_SCHEDULE_VAR1', value: 'variable 1' },
{ key: 'PIPELINE_SCHEDULE_VAR2', value: 'variable 2' },
{ key: 'PIPELINE_SCHEDULE_RAW_VAR3', value: '$REF1', raw: true },
- { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR4', value: 'interpolate ref 1 ref 2' }
+ { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR4', value: 'interpolate ref 1 ref 2' },
+ { key: 'PIPELINE_DOTENV_VAR1', value: 'variable 1' },
+ { key: 'PIPELINE_DOTENV_VAR2', value: 'variable 2' },
+ { key: 'PIPELINE_DOTENV_RAW_VAR3', value: '$REF1', raw: true },
+ { key: 'PIPELINE_DOTENV_INTERPOLATION_VAR4', value: 'interpolate ref 1 ref 2' }
+
]
expect(generator.calculate).to contain_exactly(*expected)
@@ -79,6 +94,7 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
allow(bridge).to receive(:yaml_variables).and_return([])
allow(bridge).to receive(:pipeline_variables).and_return([])
allow(bridge).to receive(:pipeline_schedule_variables).and_return([])
+ allow(bridge).to receive(:dependency_variables).and_return([])
expect(generator.calculate).to be_empty
end
@@ -105,6 +121,10 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
[{ key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
end
+ let(:pipeline_dotenv_variables) do
+ [{ key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
+ end
+
context 'when expand_file_refs is true' do
before do
allow(bridge).to receive(:expand_file_refs?).and_return(true)
@@ -114,7 +134,8 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
expected = [
{ key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
{ key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
- { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' }
+ { key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
+ { key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' }
]
expect(generator.calculate).to contain_exactly(*expected)
@@ -131,6 +152,7 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
{ key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
{ key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
{ key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
+ { key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
{ key: 'FILE_REF3', value: 'ref 3', variable_type: :file }
]
diff --git a/spec/lib/gitlab/ci/yaml_processor/test_cases/interruptible_spec.rb b/spec/lib/gitlab/ci/yaml_processor/test_cases/interruptible_spec.rb
new file mode 100644
index 00000000000..03ff7077969
--- /dev/null
+++ b/spec/lib/gitlab/ci/yaml_processor/test_cases/interruptible_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+module Gitlab
+ module Ci
+ RSpec.describe YamlProcessor, feature_category: :pipeline_composition do
+ subject(:processor) { described_class.new(config, user: nil).execute }
+
+ let(:builds) { processor.builds }
+
+ context 'with interruptible' do
+ let(:default_config) { nil }
+
+ let(:config) do
+ <<~YAML
+ #{default_config}
+
+ build1:
+ script: rspec
+ interruptible: true
+
+ build2:
+ script: rspec
+ interruptible: false
+
+ build3:
+ script: rspec
+
+ bridge1:
+ trigger: some/project
+ interruptible: true
+
+ bridge2:
+ trigger: some/project
+ interruptible: false
+
+ bridge3:
+ trigger: some/project
+ YAML
+ end
+
+ it 'returns jobs with their interruptible value' do
+ expect(builds).to contain_exactly(
+ a_hash_including(name: 'build1', interruptible: true),
+ a_hash_including(name: 'build2', interruptible: false),
+ a_hash_including(name: 'build3').and(exclude(:interruptible)),
+ a_hash_including(name: 'bridge1', interruptible: true),
+ a_hash_including(name: 'bridge2', interruptible: false),
+ a_hash_including(name: 'bridge3').and(exclude(:interruptible))
+ )
+ end
+
+ context 'when default:interruptible is true' do
+ let(:default_config) do
+ <<~YAML
+ default:
+ interruptible: true
+ YAML
+ end
+
+ it 'returns jobs with their interruptible value' do
+ expect(builds).to contain_exactly(
+ a_hash_including(name: 'build1', interruptible: true),
+ a_hash_including(name: 'build2', interruptible: false),
+ a_hash_including(name: 'build3', interruptible: true),
+ a_hash_including(name: 'bridge1', interruptible: true),
+ a_hash_including(name: 'bridge2', interruptible: false),
+ a_hash_including(name: 'bridge3', interruptible: true)
+ )
+ end
+ end
+
+ context 'when default:interruptible is false' do
+ let(:default_config) do
+ <<~YAML
+ default:
+ interruptible: false
+ YAML
+ end
+
+ it 'returns jobs with their interruptible value' do
+ expect(builds).to contain_exactly(
+ a_hash_including(name: 'build1', interruptible: true),
+ a_hash_including(name: 'build2', interruptible: false),
+ a_hash_including(name: 'build3', interruptible: false),
+ a_hash_including(name: 'bridge1', interruptible: true),
+ a_hash_including(name: 'bridge2', interruptible: false),
+ a_hash_including(name: 'bridge3', interruptible: false)
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index f01c1c7d053..844a6849c8f 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -123,55 +123,6 @@ module Gitlab
end
end
- describe 'interruptible entry' do
- describe 'interruptible job' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec', interruptible: true })
- end
-
- it { expect(rspec_build[:interruptible]).to be_truthy }
- end
-
- describe 'interruptible job with default value' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec' })
- end
-
- it { expect(rspec_build).not_to have_key(:interruptible) }
- end
-
- describe 'uninterruptible job' do
- let(:config) do
- YAML.dump(rspec: { script: 'rspec', interruptible: false })
- end
-
- it { expect(rspec_build[:interruptible]).to be_falsy }
- end
-
- it "returns interruptible when overridden for job" do
- config = YAML.dump({ default: { interruptible: true },
- rspec: { script: "rspec" } })
-
- config_processor = described_class.new(config).execute
- builds = config_processor.builds.select { |b| b[:stage] == "test" }
-
- expect(builds.size).to eq(1)
- expect(builds.first).to eq({
- stage: "test",
- stage_idx: 2,
- name: "rspec",
- only: { refs: %w[branches tags] },
- options: { script: ["rspec"] },
- interruptible: true,
- allow_failure: false,
- when: "on_success",
- job_variables: [],
- root_variables_inheritance: true,
- scheduling_type: :stage
- })
- end
- end
-
describe 'retry entry' do
context 'when retry count is specified' do
let(:config) do
@@ -544,6 +495,27 @@ module Gitlab
expect(subject.workflow_name).to be_nil
end
end
+
+ context 'with auto_cancel' do
+ let(:config) do
+ <<-YML
+ workflow:
+ auto_cancel:
+ on_new_commit: interruptible
+ on_job_failure: all
+
+ hello:
+ script: echo world
+ YML
+ end
+
+ it 'parses the workflow:auto_cancel as workflow_auto_cancel' do
+ expect(subject.workflow_auto_cancel).to eq({
+ on_new_commit: 'interruptible',
+ on_job_failure: 'all'
+ })
+ end
+ end
end
describe '#warnings' do
@@ -1313,6 +1285,46 @@ module Gitlab
})
end
end
+
+ context 'when image and service have docker options' do
+ let(:config) do
+ <<~YAML
+ test:
+ script: exit 0
+ image:
+ name: ruby:2.7
+ docker:
+ platform: linux/amd64
+ services:
+ - name: postgres:11.9
+ docker:
+ platform: linux/amd64
+ YAML
+ end
+
+ it { is_expected.to be_valid }
+
+ it "returns with image" do
+ expect(processor.builds).to contain_exactly({
+ stage: "test",
+ stage_idx: 2,
+ name: "test",
+ only: { refs: %w[branches tags] },
+ options: {
+ script: ["exit 0"],
+ image: { name: "ruby:2.7",
+ executor_opts: { docker: { platform: 'linux/amd64' } } },
+ services: [{ name: "postgres:11.9",
+ executor_opts: { docker: { platform: 'linux/amd64' } } }]
+ },
+ allow_failure: false,
+ when: "on_success",
+ job_variables: [],
+ root_variables_inheritance: true,
+ scheduling_type: :stage
+ })
+ end
+ end
end
describe 'Variables' do