summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/ci
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-20 13:49:51 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-20 13:49:51 +0000
commit71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch)
tree6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /spec/lib/gitlab/ci
parenta7253423e3403b8c08f8a161e5937e1488f5f407 (diff)
downloadgitlab-ce-71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e.tar.gz
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r--spec/lib/gitlab/ci/artifacts/logger_spec.rb36
-rw-r--r--spec/lib/gitlab/ci/build/auto_retry_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/components/instance_path_spec.rb116
-rw-r--r--spec/lib/gitlab/ci/config/entry/include_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/external/context_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/config/external/file/artifact_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/external/file/component_spec.rb179
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb53
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb78
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/external/file/template_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb91
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/config/external/rules_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/yaml_spec.rb105
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/interpolation/access_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/interpolation/block_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/interpolation/config_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/interpolation/context_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/interpolation/template_spec.rb102
-rw-r--r--spec/lib/gitlab/ci/parsers/instrumentation_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/parsers/security/common_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb202
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb72
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/metrics_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb1615
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_spec.rb115
-rw-r--r--spec/lib/gitlab/ci/runner_instructions_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/runner_releases_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/runner_upgrade_check_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/status/bridge/common_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/bridge/factory_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/templates/Terraform/module_base_gitlab_ci_yaml_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/templates/terraform_module_gitlab_ci_yaml_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/trace/archive_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb336
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb2
49 files changed, 2478 insertions, 1328 deletions
diff --git a/spec/lib/gitlab/ci/artifacts/logger_spec.rb b/spec/lib/gitlab/ci/artifacts/logger_spec.rb
index 7753cb0d25e..7a2f8b6ea37 100644
--- a/spec/lib/gitlab/ci/artifacts/logger_spec.rb
+++ b/spec/lib/gitlab/ci/artifacts/logger_spec.rb
@@ -9,23 +9,27 @@ RSpec.describe Gitlab::Ci::Artifacts::Logger do
describe '.log_created' do
it 'logs information about created artifact' do
- artifact = create(:ci_job_artifact, :archive)
-
- expect(Gitlab::AppLogger).to receive(:info).with(
- hash_including(
- message: 'Artifact created',
- job_artifact_id: artifact.id,
- size: artifact.size,
- type: artifact.file_type,
- build_id: artifact.job_id,
- project_id: artifact.project_id,
- 'correlation_id' => an_instance_of(String),
- 'meta.feature_category' => 'test',
- 'meta.caller_id' => 'caller'
+ artifact_1 = create(:ci_job_artifact, :archive)
+ artifact_2 = create(:ci_job_artifact, :metadata)
+ artifacts = [artifact_1, artifact_2]
+
+ artifacts.each do |artifact|
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Artifact created',
+ job_artifact_id: artifact.id,
+ size: artifact.size,
+ file_type: artifact.file_type,
+ build_id: artifact.job_id,
+ project_id: artifact.project_id,
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
)
- )
+ end
- described_class.log_created(artifact)
+ described_class.log_created(artifacts)
end
end
@@ -43,7 +47,7 @@ RSpec.describe Gitlab::Ci::Artifacts::Logger do
job_artifact_id: artifact.id,
expire_at: artifact.expire_at,
size: artifact.size,
- type: artifact.file_type,
+ file_type: artifact.file_type,
build_id: artifact.job_id,
project_id: artifact.project_id,
method: method,
diff --git a/spec/lib/gitlab/ci/build/auto_retry_spec.rb b/spec/lib/gitlab/ci/build/auto_retry_spec.rb
index 9ff9200322e..314714c543b 100644
--- a/spec/lib/gitlab/ci/build/auto_retry_spec.rb
+++ b/spec/lib/gitlab/ci/build/auto_retry_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Build::AutoRetry do
+RSpec.describe Gitlab::Ci::Build::AutoRetry, feature_category: :pipeline_authoring do
let(:auto_retry) { described_class.new(build) }
describe '#allowed?' do
@@ -112,5 +112,13 @@ RSpec.describe Gitlab::Ci::Build::AutoRetry do
expect(result).to eq ['always']
end
end
+
+ context 'with retry[:when] set to nil' do
+ let(:build) { create(:ci_build, options: { retry: { when: nil } }) }
+
+ it 'returns always array' do
+ expect(result).to eq ['always']
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb
index 31c7437cfe0..ebdb738f10b 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb
@@ -1,10 +1,8 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
-require 'support/helpers/stubbed_feature'
-require 'support/helpers/stub_feature_flags'
+require 'spec_helper'
-RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::If do
+RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::If, feature_category: :continuous_integration do
include StubFeatureFlags
subject(:if_clause) { described_class.new(expression) }
diff --git a/spec/lib/gitlab/ci/components/instance_path_spec.rb b/spec/lib/gitlab/ci/components/instance_path_spec.rb
new file mode 100644
index 00000000000..d9beae0555c
--- /dev/null
+++ b/spec/lib/gitlab/ci/components/instance_path_spec.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline_authoring do
+ let_it_be(:user) { create(:user) }
+
+ let(:path) { described_class.new(address: address, content_filename: 'template.yml') }
+ let(:settings) { Settingslogic.new({ 'component_fqdn' => current_host }) }
+ let(:current_host) { 'acme.com/' }
+
+ before do
+ allow(::Settings).to receive(:gitlab_ci).and_return(settings)
+ end
+
+ describe 'FQDN path' do
+ let_it_be(:existing_project) { create(:project, :repository) }
+
+ let(:project_path) { existing_project.full_path }
+ let(:address) { "acme.com/#{project_path}/component@#{version}" }
+ let(:version) { 'master' }
+
+ context 'when project exists' do
+ it 'provides the expected attributes', :aggregate_failures do
+ expect(path.project).to eq(existing_project)
+ expect(path.host).to eq(current_host)
+ expect(path.sha).to eq(existing_project.commit('master').id)
+ expect(path.project_file_path).to eq('component/template.yml')
+ end
+
+ context 'when content exists' do
+ let(:content) { 'image: alpine' }
+
+ before do
+ allow_next_instance_of(Repository) do |instance|
+ allow(instance)
+ .to receive(:blob_data_at)
+ .with(existing_project.commit('master').id, 'component/template.yml')
+ .and_return(content)
+ end
+ end
+
+ context 'when user has permissions to read code' do
+ before do
+ existing_project.add_developer(user)
+ end
+
+ it 'fetches the content' do
+ expect(path.fetch_content!(current_user: user)).to eq(content)
+ end
+ end
+
+ context 'when user does not have permissions to download code' do
+ it 'raises an error when fetching the content' do
+ expect { path.fetch_content!(current_user: user) }
+ .to raise_error(Gitlab::Access::AccessDeniedError)
+ end
+ end
+ end
+ end
+
+ context 'when project path is nested under a subgroup' do
+ let(:existing_group) { create(:group, :nested) }
+ let(:existing_project) { create(:project, :repository, group: existing_group) }
+
+ it 'provides the expected attributes', :aggregate_failures do
+ expect(path.project).to eq(existing_project)
+ expect(path.host).to eq(current_host)
+ expect(path.sha).to eq(existing_project.commit('master').id)
+ expect(path.project_file_path).to eq('component/template.yml')
+ 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 'provides the expected attributes', :aggregate_failures do
+ expect(path.project).to eq(existing_project)
+ expect(path.host).to eq(current_host)
+ expect(path.sha).to eq(existing_project.commit('master').id)
+ expect(path.project_file_path).to eq('component/template.yml')
+ end
+ end
+
+ context 'when version does not exist' do
+ let(:version) { 'non-existent' }
+
+ it 'provides the expected attributes', :aggregate_failures do
+ expect(path.project).to eq(existing_project)
+ expect(path.host).to eq(current_host)
+ expect(path.sha).to be_nil
+ expect(path.project_file_path).to eq('component/template.yml')
+ end
+
+ it 'returns nil when fetching the content' do
+ expect(path.fetch_content!(current_user: user)).to be_nil
+ end
+ end
+
+ context 'when project does not exist' do
+ let(:project_path) { 'non-existent/project' }
+
+ it 'provides the expected attributes', :aggregate_failures do
+ expect(path.project).to be_nil
+ expect(path.host).to eq(current_host)
+ expect(path.sha).to be_nil
+ expect(path.project_file_path).to be_nil
+ end
+
+ it 'returns nil when fetching the content' do
+ expect(path.fetch_content!(current_user: user)).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/include_spec.rb b/spec/lib/gitlab/ci/config/entry/include_spec.rb
index fd7f85c9298..5eecff5b592 100644
--- a/spec/lib/gitlab/ci/config/entry/include_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include_spec.rb
@@ -44,6 +44,12 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Include do
it { is_expected.to be_valid }
end
+ context 'when using "component"' do
+ let(:config) { { component: 'path/to/component@1.0' } }
+
+ it { is_expected.to be_valid }
+ end
+
context 'when using "artifact"' do
context 'and specifying "job"' do
let(:config) { { artifact: 'test.yml', job: 'generator' } }
diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb
index 40702e75404..1fd3cf3c99f 100644
--- a/spec/lib/gitlab/ci/config/external/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/context_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::Context do
+RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipeline_authoring do
let(:project) { build(:project) }
let(:user) { double('User') }
let(:sha) { '12345' }
@@ -14,7 +14,8 @@ RSpec.describe Gitlab::Ci::Config::External::Context do
describe 'attributes' do
context 'with values' do
it { is_expected.to have_attributes(**attributes) }
- it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.expandset).to eq([]) }
+ it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::NEW_MAX_INCLUDES) }
it { expect(subject.execution_deadline).to eq(0) }
it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
@@ -25,11 +26,39 @@ RSpec.describe Gitlab::Ci::Config::External::Context do
let(:attributes) { { project: nil, user: nil, sha: nil } }
it { is_expected.to have_attributes(**attributes) }
- it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.expandset).to eq([]) }
+ it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::NEW_MAX_INCLUDES) }
it { expect(subject.execution_deadline).to eq(0) }
it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
end
+
+ context 'when FF ci_includes_count_duplicates is disabled' do
+ before do
+ stub_feature_flags(ci_includes_count_duplicates: false)
+ end
+
+ context 'with values' do
+ it { is_expected.to have_attributes(**attributes) }
+ it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::MAX_INCLUDES) }
+ it { expect(subject.execution_deadline).to eq(0) }
+ it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
+ it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
+ it { expect(subject.variables_hash).to include('a' => 'b') }
+ end
+
+ context 'without values' do
+ let(:attributes) { { project: nil, user: nil, sha: nil } }
+
+ it { is_expected.to have_attributes(**attributes) }
+ it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::MAX_INCLUDES) }
+ it { expect(subject.execution_deadline).to eq(0) }
+ it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
+ it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
+ end
+ end
end
describe '#set_deadline' do
diff --git a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
index a8dc7897082..45a15fb5f36 100644
--- a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
+RSpec.describe Gitlab::Ci::Config::External::File::Artifact, feature_category: :pipeline_authoring do
let(:parent_pipeline) { create(:ci_pipeline) }
let(:variables) {}
let(:context) do
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
describe '#valid?' do
subject(:valid?) do
- external_file.validate!
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([external_file])
external_file.valid?
end
@@ -162,7 +162,8 @@ RSpec.describe Gitlab::Ci::Config::External::File::Artifact do
user: anything
}
expect(context).to receive(:mutate).with(expected_attrs).and_call_original
- external_file.validate!
+
+ expect(valid?).to be_truthy
external_file.content
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index 8475c3a8b19..55d95d0c1f8 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::File::Base do
+RSpec.describe Gitlab::Ci::Config::External::File::Base, feature_category: :pipeline_authoring do
let(:variables) {}
let(:context_params) { { sha: 'HEAD', variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
describe '#valid?' do
subject(:valid?) do
- file.validate!
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([file])
file.valid?
end
diff --git a/spec/lib/gitlab/ci/config/external/file/component_spec.rb b/spec/lib/gitlab/ci/config/external/file/component_spec.rb
new file mode 100644
index 00000000000..a162a1a8abf
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/file/component_spec.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::External::File::Component, feature_category: :pipeline_authoring do
+ let_it_be(:context_project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project_variables) { project.predefined_variables }
+
+ let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
+ let(:external_resource) { described_class.new(params, context) }
+ let(:params) { { component: 'gitlab.com/acme/components/my-component@1.0' } }
+ let(:fetch_service) { instance_double(::Ci::Components::FetchService) }
+ let(:response) { ServiceResponse.error(message: 'some error message') }
+
+ let(:context_params) do
+ {
+ project: context_project,
+ sha: '12345',
+ user: user,
+ variables: project_variables
+ }
+ end
+
+ before do
+ allow(::Ci::Components::FetchService)
+ .to receive(:new)
+ .with(
+ address: params[:component],
+ current_user: context.user
+ ).and_return(fetch_service)
+
+ allow(fetch_service).to receive(:execute).and_return(response)
+ end
+
+ describe '#matching?' do
+ subject(:matching) { external_resource.matching? }
+
+ context 'when component is specified' do
+ let(:params) { { component: 'some-value' } }
+
+ it { is_expected.to be_truthy }
+
+ context 'when feature flag ci_include_components is disabled' do
+ before do
+ stub_feature_flags(ci_include_components: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when component is not specified' do
+ let(:params) { { local: 'some-value' } }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#valid?' do
+ subject(:valid?) do
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([external_resource])
+ external_resource.valid?
+ end
+
+ context 'when the context project does not have a repository' do
+ before do
+ allow(context_project).to receive(:repository).and_return(nil)
+ end
+
+ it 'is invalid' do
+ expect(subject).to be_falsy
+ expect(external_resource.error_message).to eq('Unable to use components outside of a project context')
+ end
+ end
+
+ context 'when location is not provided' do
+ let(:params) { { component: 123 } }
+
+ it 'is invalid' do
+ expect(subject).to be_falsy
+ expect(external_resource.error_message).to eq('Included file `123` needs to be a string')
+ end
+ end
+
+ context 'when component path is provided' do
+ context 'when component is not found' do
+ let(:response) do
+ ServiceResponse.error(message: 'Content not found')
+ end
+
+ it 'is invalid' do
+ expect(subject).to be_falsy
+ expect(external_resource.error_message).to eq('Content not found')
+ end
+ end
+
+ context 'when component is found' do
+ let(:content) do
+ <<~COMPONENT
+ job:
+ script: echo
+ COMPONENT
+ end
+
+ let(:response) do
+ ServiceResponse.success(payload: {
+ content: content,
+ path: instance_double(::Gitlab::Ci::Components::InstancePath, project: project, sha: '12345')
+ })
+ end
+
+ it 'is valid' do
+ expect(subject).to be_truthy
+ expect(external_resource.content).to eq(content)
+ end
+
+ context 'when content is not a valid YAML' do
+ let(:content) { 'the-content' }
+
+ it 'is invalid' do
+ expect(subject).to be_falsy
+ expect(external_resource.error_message).to match(/does not have valid YAML syntax/)
+ end
+ end
+ end
+ end
+ end
+
+ describe '#metadata' do
+ subject(:metadata) { external_resource.metadata }
+
+ let(:component_path) do
+ instance_double(::Gitlab::Ci::Components::InstancePath,
+ project: project,
+ sha: '12345',
+ project_file_path: 'my-component/template.yml')
+ end
+
+ let(:response) do
+ ServiceResponse.success(payload: { path: component_path })
+ end
+
+ it 'returns the metadata' do
+ is_expected.to include(
+ context_project: context_project.full_path,
+ context_sha: context.sha,
+ type: :component,
+ location: 'gitlab.com/acme/components/my-component@1.0',
+ blob: a_string_ending_with("#{project.full_path}/-/blob/12345/my-component/template.yml"),
+ raw: nil,
+ extra: {}
+ )
+ end
+ end
+
+ describe '#expand_context' do
+ let(:component_path) do
+ instance_double(::Gitlab::Ci::Components::InstancePath,
+ project: project,
+ sha: '12345')
+ end
+
+ let(:response) do
+ ServiceResponse.success(payload: { path: component_path })
+ end
+
+ subject { external_resource.send(:expand_context_attrs) }
+
+ it 'inherits user and variables while changes project and sha' do
+ is_expected.to include(
+ project: project,
+ sha: '12345',
+ user: context.user,
+ variables: context.variables)
+ 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 a77acb45978..b5895b4bc81 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -30,6 +30,40 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
.to receive(:check_execution_time!)
end
+ describe '.initialize' do
+ context 'when a local is specified' do
+ let(:params) { { local: 'file' } }
+
+ it 'sets the location' do
+ expect(local_file.location).to eq('file')
+ end
+
+ context 'when the local is prefixed with a slash' do
+ let(:params) { { local: '/file' } }
+
+ it 'removes the slash' do
+ expect(local_file.location).to eq('file')
+ end
+ end
+
+ context 'when the local is prefixed with multiple slashes' do
+ let(:params) { { local: '//file' } }
+
+ it 'removes slashes' do
+ expect(local_file.location).to eq('file')
+ end
+ end
+ end
+
+ context 'with a missing local' do
+ let(:params) { { local: nil } }
+
+ it 'sets the location to an empty string' do
+ expect(local_file.location).to eq('')
+ end
+ end
+ end
+
describe '#matching?' do
context 'when a local is specified' do
let(:params) { { local: 'file' } }
@@ -58,7 +92,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
describe '#valid?' do
subject(:valid?) do
- local_file.validate!
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([local_file])
local_file.valid?
end
@@ -88,10 +122,13 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret', 'masked' => true }]) }
let(:location) { '/lib/gitlab/ci/templates/secret/existent-file.yml' }
- it 'returns false and adds an error message about an empty file' do
+ before do
allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("")
- local_file.validate!
- expect(local_file.errors).to include("Local file `/lib/gitlab/ci/templates/xxxxxx/existent-file.yml` is empty!")
+ end
+
+ it 'returns false and adds an error message about an empty file' do
+ expect(valid?).to be_falsy
+ expect(local_file.errors).to include("Local file `lib/gitlab/ci/templates/xxxxxx/existent-file.yml` is empty!")
end
end
@@ -101,7 +138,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
it 'returns false and adds an error message stating that included file does not exist' do
expect(valid?).to be_falsy
- expect(local_file.errors).to include("Sha #{sha} is not valid!")
+ expect(local_file.errors).to include("Local file `lib/gitlab/ci/templates/existent-file.yml` does not exist!")
end
end
end
@@ -143,11 +180,11 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
before do
- local_file.validate!
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([local_file])
end
it 'returns an error message' do
- expect(local_file.error_message).to eq("Local file `/lib/gitlab/ci/templates/xxxxxxxxxxx.yml` does not exist!")
+ expect(local_file.error_message).to eq("Local file `lib/gitlab/ci/templates/xxxxxxxxxxx.yml` does not exist!")
end
end
@@ -203,7 +240,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local, feature_category: :pip
context_project: project.full_path,
context_sha: sha,
type: :local,
- location: '/lib/gitlab/ci/templates/existent-file.yml',
+ 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",
extra: {}
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index 0ba92d1e92d..abe38cdbc3e 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::File::Project do
+RSpec.describe Gitlab::Ci::Config::External::File::Project, feature_category: :pipeline_authoring do
+ include RepoHelpers
+
let_it_be(:context_project) { create(:project) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
@@ -12,11 +14,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:project_file) { described_class.new(params, context) }
let(:variables) { project.predefined_variables.to_runner_variables }
+ let(:project_sha) { project.commit.sha }
let(:context_params) do
{
project: context_project,
- sha: '12345',
+ sha: project_sha,
user: context_user,
parent_pipeline: parent_pipeline,
variables: variables
@@ -67,7 +70,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
describe '#valid?' do
subject(:valid?) do
- project_file.validate!
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([project_file])
project_file.valid?
end
@@ -76,10 +79,10 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
{ project: project.full_path, file: '/file.yml' }
end
- let(:root_ref_sha) { project.repository.root_ref_sha }
-
- before do
- stub_project_blob(root_ref_sha, '/file.yml') { 'image: image:1.0' }
+ around do |example|
+ create_and_delete_files(project, { '/file.yml' => 'image: image:1.0' }) do
+ example.run
+ end
end
it { is_expected.to be_truthy }
@@ -99,10 +102,10 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
{ project: project.full_path, ref: 'master', file: '/file.yml' }
end
- let(:ref_sha) { project.commit('master').sha }
-
- before do
- stub_project_blob(ref_sha, '/file.yml') { 'image: image:1.0' }
+ around do |example|
+ create_and_delete_files(project, { '/file.yml' => 'image: image:1.0' }) do
+ example.run
+ end
end
it { is_expected.to be_truthy }
@@ -114,15 +117,16 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
end
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
- let(:root_ref_sha) { project.repository.root_ref_sha }
- before do
- stub_project_blob(root_ref_sha, '/secret_file.yml') { '' }
+ around do |example|
+ create_and_delete_files(project, { '/secret_file.yml' => '' }) do
+ example.run
+ end
end
it 'returns false' do
expect(valid?).to be_falsy
- expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxx.yml` is empty!")
+ expect(project_file.error_message).to include("Project `#{project.full_path}` file `xxxxxxxxxxx.yml` is empty!")
end
end
@@ -146,7 +150,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
it 'returns false' do
expect(valid?).to be_falsy
- expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxxxxxxxxxx.yml` does not exist!")
+ expect(project_file.error_message).to include("Project `#{project.full_path}` file `xxxxxxxxxxxxxxxxxxx.yml` does not exist!")
end
end
@@ -157,7 +161,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
it 'returns false' do
expect(valid?).to be_falsy
- expect(project_file.error_message).to include('Included file `/invalid-file` does not have YAML extension!')
+ expect(project_file.error_message).to include('Included file `invalid-file` does not have YAML extension!')
end
end
@@ -200,7 +204,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
is_expected.to include(
user: user,
project: project,
- sha: project.commit('master').id,
+ sha: project_sha,
parent_pipeline: parent_pipeline,
variables: project.predefined_variables.to_runner_variables)
end
@@ -216,45 +220,43 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
it {
is_expected.to eq(
context_project: context_project.full_path,
- context_sha: '12345',
+ context_sha: project_sha,
type: :file,
- location: '/file.yml',
- blob: "http://localhost/#{project.full_path}/-/blob/#{project.commit('master').id}/file.yml",
- raw: "http://localhost/#{project.full_path}/-/raw/#{project.commit('master').id}/file.yml",
+ location: 'file.yml',
+ blob: "http://localhost/#{project.full_path}/-/blob/#{project_sha}/file.yml",
+ raw: "http://localhost/#{project.full_path}/-/raw/#{project_sha}/file.yml",
extra: { project: project.full_path, ref: 'HEAD' }
)
}
context 'when project name and ref include masked variables' do
+ let(:project_name) { 'my_project_name' }
+ let(:branch_name) { 'merge-commit-analyze-after' }
+ let(:project) { create(:project, :repository, name: project_name) }
+ let(:namespace_path) { project.namespace.full_path }
+ let(:included_project_sha) { project.commit(branch_name).sha }
+
let(:variables) do
Gitlab::Ci::Variables::Collection.new(
[
- { key: 'VAR1', value: 'a_secret_variable_value1', masked: true },
- { key: 'VAR2', value: 'a_secret_variable_value2', masked: true }
+ { key: 'VAR1', value: project_name, masked: true },
+ { key: 'VAR2', value: branch_name, masked: true }
])
end
- let(:params) { { project: 'a_secret_variable_value1', ref: 'a_secret_variable_value2', file: '/file.yml' } }
+ let(:params) { { project: project.full_path, ref: branch_name, file: '/file.yml' } }
it {
is_expected.to eq(
context_project: context_project.full_path,
- context_sha: '12345',
+ context_sha: project_sha,
type: :file,
- location: '/file.yml',
- blob: nil,
- raw: nil,
- extra: { project: 'xxxxxxxxxxxxxxxxxxxxxxxx', ref: 'xxxxxxxxxxxxxxxxxxxxxxxx' }
+ location: 'file.yml',
+ blob: "http://localhost/#{namespace_path}/xxxxxxxxxxxxxxx/-/blob/#{included_project_sha}/file.yml",
+ raw: "http://localhost/#{namespace_path}/xxxxxxxxxxxxxxx/-/raw/#{included_project_sha}/file.yml",
+ extra: { project: "#{namespace_path}/xxxxxxxxxxxxxxx", ref: 'xxxxxxxxxxxxxxxxxxxxxxxxxx' }
)
}
end
end
-
- private
-
- def stub_project_blob(ref, path)
- allow_next_instance_of(Repository) do |instance|
- allow(instance).to receive(:blob_data_at).with(ref, path) { yield }
- end
- end
end
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 8d93cdcf378..2ce3c257a43 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::File::Remote do
+RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pipeline_authoring do
include StubRequests
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
@@ -55,7 +55,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
describe "#valid?" do
subject(:valid?) do
- remote_file.validate!
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([remote_file])
remote_file.valid?
end
@@ -138,7 +138,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote do
describe "#error_message" do
subject(:error_message) do
- remote_file.validate!
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([remote_file])
remote_file.error_message
end
diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
index 074e7a1d32d..83e98874118 100644
--- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::File::Template do
+RSpec.describe Gitlab::Ci::Config::External::File::Template, feature_category: :pipeline_authoring do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
@@ -46,7 +46,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Template do
describe "#valid?" do
subject(:valid?) do
- template_file.validate!
+ Gitlab::Ci::Config::External::Mapper::Verifier.new(context).process([template_file])
template_file.valid?
end
diff --git a/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
index 5f321a696c9..11c79e19cff 100644
--- a/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper/matcher_spec.rb
@@ -17,11 +17,14 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category:
describe '#process' do
let(:locations) do
- [{ local: 'file.yml' },
- { file: 'file.yml', project: 'namespace/project' },
- { remote: 'https://example.com/.gitlab-ci.yml' },
- { template: 'file.yml' },
- { artifact: 'generated.yml', job: 'test' }]
+ [
+ { local: 'file.yml' },
+ { file: 'file.yml', project: 'namespace/project' },
+ { component: 'gitlab.com/org/component@1.0' },
+ { remote: 'https://example.com/.gitlab-ci.yml' },
+ { template: 'file.yml' },
+ { artifact: 'generated.yml', job: 'test' }
+ ]
end
subject(:process) { matcher.process(locations) }
@@ -30,6 +33,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category:
is_expected.to contain_exactly(
an_instance_of(Gitlab::Ci::Config::External::File::Local),
an_instance_of(Gitlab::Ci::Config::External::File::Project),
+ an_instance_of(Gitlab::Ci::Config::External::File::Component),
an_instance_of(Gitlab::Ci::Config::External::File::Remote),
an_instance_of(Gitlab::Ci::Config::External::File::Template),
an_instance_of(Gitlab::Ci::Config::External::File::Artifact)
@@ -42,8 +46,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category:
it 'raises an error' do
expect { process }.to raise_error(
Gitlab::Ci::Config::External::Mapper::AmbigiousSpecificationError,
- '`{"invalid":"file.yml"}` does not have a valid subkey for include. ' \
- 'Valid subkeys are: `local`, `project`, `remote`, `template`, `artifact`'
+ /`{"invalid":"file.yml"}` does not have a valid subkey for include. Valid subkeys are:/
)
end
@@ -53,8 +56,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category:
it 'raises an error with a masked sentence' do
expect { process }.to raise_error(
Gitlab::Ci::Config::External::Mapper::AmbigiousSpecificationError,
- '`{"invalid":"xxxxxxxxxxxxxx.yml"}` does not have a valid subkey for include. ' \
- 'Valid subkeys are: `local`, `project`, `remote`, `template`, `artifact`'
+ /`{"invalid":"xxxxxxxxxxxxxx.yml"}` does not have a valid subkey for include. Valid subkeys are:/
)
end
end
@@ -66,7 +68,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Matcher, feature_category:
it 'raises an error' do
expect { process }.to raise_error(
Gitlab::Ci::Config::External::Mapper::AmbigiousSpecificationError,
- "Each include must use only one of: `local`, `project`, `remote`, `template`, `artifact`"
+ /Each include must use only one of:/
)
end
end
diff --git a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
index 7c7252c6b0e..a219666f24e 100644
--- a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
@@ -25,6 +25,10 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
my_test:
script: echo Hello World
YAML
+ 'myfolder/file3.yml' => <<~YAML,
+ my_deploy:
+ script: echo Hello World
+ YAML
'nested_configs.yml' => <<~YAML
include:
- local: myfolder/file1.yml
@@ -58,16 +62,63 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
let(:files) do
[
Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
- Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context)
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file3.yml' }, context)
]
end
it 'returns an array of file objects' do
- expect(process.map(&:location)).to contain_exactly('myfolder/file1.yml', 'myfolder/file2.yml')
+ expect(process.map(&:location)).to contain_exactly(
+ 'myfolder/file1.yml', 'myfolder/file2.yml', 'myfolder/file3.yml'
+ )
+ end
+
+ it 'adds files to the expandset' do
+ expect { process }.to change { context.expandset.count }.by(3)
+ end
+
+ it 'calls Gitaly only once for all files', :request_store do
+ # 1 for project.commit.id, 1 for the files
+ expect { process }.to change { Gitlab::GitalyClient.get_request_count }.by(2)
+ end
+ end
+
+ context 'when files are project files' do
+ let_it_be(:included_project) { create(:project, :repository, namespace: project.namespace, creator: user) }
+
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Project.new(
+ { file: 'myfolder/file1.yml', project: included_project.full_path }, context
+ ),
+ Gitlab::Ci::Config::External::File::Project.new(
+ { file: 'myfolder/file2.yml', project: included_project.full_path }, context
+ ),
+ Gitlab::Ci::Config::External::File::Project.new(
+ { file: 'myfolder/file3.yml', project: included_project.full_path }, context
+ )
+ ]
+ end
+
+ around(:all) do |example|
+ create_and_delete_files(included_project, project_files) do
+ example.run
+ end
+ end
+
+ it 'returns an array of file objects' do
+ expect(process.map(&:location)).to contain_exactly(
+ 'myfolder/file1.yml', 'myfolder/file2.yml', 'myfolder/file3.yml'
+ )
end
it 'adds files to the expandset' do
- expect { process }.to change { context.expandset.count }.by(2)
+ expect { process }.to change { context.expandset.count }.by(3)
+ end
+
+ it 'calls Gitaly only once for all files', :request_store do
+ # 1 for project.commit.id, 3 for the sha check, 1 for the files
+ expect { process }.to change { Gitlab::GitalyClient.get_request_count }.by(5)
end
end
@@ -99,7 +150,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
end
end
- context 'when max_includes is exceeded' do
+ context 'when total file count exceeds max_includes' do
context 'when files are nested' do
let(:files) do
[
@@ -107,11 +158,8 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
]
end
- before do
- allow(context).to receive(:max_includes).and_return(1)
- end
-
it 'raises Processor::IncludeError' do
+ allow(context).to receive(:max_includes).and_return(1)
expect { process }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError)
end
end
@@ -124,13 +172,36 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
]
end
- before do
+ it 'raises Mapper::TooManyIncludesError' do
allow(context).to receive(:max_includes).and_return(1)
+ expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError)
end
+ end
- it 'raises Mapper::TooManyIncludesError' do
+ context 'when files are duplicates' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context)
+ ]
+ end
+
+ it 'raises error' do
+ allow(context).to receive(:max_includes).and_return(2)
expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError)
end
+
+ context 'when FF ci_includes_count_duplicates is disabled' do
+ before do
+ stub_feature_flags(ci_includes_count_duplicates: false)
+ end
+
+ it 'does not raise error' do
+ allow(context).to receive(:max_includes).and_return(2)
+ expect { process }.not_to raise_error
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 9d0e57d4292..b3115617084 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -2,9 +2,7 @@
require 'spec_helper'
-# This will be use with the FF ci_refactoring_external_mapper_verifier in the next MR.
-# It can be removed when the FF is removed.
-RSpec.shared_context 'gitlab_ci_config_external_mapper' do
+RSpec.describe Gitlab::Ci::Config::External::Mapper, feature_category: :pipeline_authoring do
include StubRequests
include RepoHelpers
@@ -124,7 +122,7 @@ RSpec.shared_context 'gitlab_ci_config_external_mapper' do
end
it 'returns ambigious specification error' do
- expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, '`{"invalid":"secret-file.yml"}` does not have a valid subkey for include. Valid subkeys are: `local`, `project`, `remote`, `template`, `artifact`')
+ expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, /`{"invalid":"secret-file.yml"}` does not have a valid subkey for include. Valid subkeys are:/)
end
end
@@ -138,7 +136,7 @@ RSpec.shared_context 'gitlab_ci_config_external_mapper' do
end
it 'returns ambigious specification error' do
- expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, 'Each include must use only one of: `local`, `project`, `remote`, `template`, `artifact`')
+ expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, /Each include must use only one of/)
end
end
@@ -168,7 +166,7 @@ RSpec.shared_context 'gitlab_ci_config_external_mapper' do
an_instance_of(Gitlab::Ci::Config::External::File::Project))
end
- it_behaves_like 'logging config file fetch', 'config_file_fetch_project_content_duration_s', 2
+ it_behaves_like 'logging config file fetch', 'config_file_fetch_project_content_duration_s', 1
end
end
@@ -232,9 +230,20 @@ RSpec.shared_context 'gitlab_ci_config_external_mapper' do
expect { process }.not_to raise_error
end
- it 'has expanset with one' do
+ it 'has expanset with two' do
process
- expect(context.expandset.size).to eq(1)
+ expect(context.expandset.size).to eq(2)
+ end
+
+ context 'when FF ci_includes_count_duplicates is disabled' do
+ before do
+ stub_feature_flags(ci_includes_count_duplicates: false)
+ end
+
+ it 'has expanset with one' do
+ process
+ expect(context.expandset.size).to eq(1)
+ end
end
end
@@ -464,7 +473,3 @@ RSpec.shared_context 'gitlab_ci_config_external_mapper' do
end
end
end
-
-RSpec.describe Gitlab::Ci::Config::External::Mapper, feature_category: :pipeline_authoring do
- it_behaves_like 'gitlab_ci_config_external_mapper'
-end
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index c9efaf2e1af..bb65c2ef10c 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -52,7 +52,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
it 'raises an error' do
expect { processor.perform }.to raise_error(
described_class::IncludeError,
- "Local file `/lib/gitlab/ci/templates/non-existent-file.yml` does not exist!"
+ "Local file `lib/gitlab/ci/templates/non-existent-file.yml` does not exist!"
)
end
end
@@ -221,7 +221,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
it 'raises an error' do
expect { processor.perform }.to raise_error(
described_class::IncludeError,
- "Included file `/lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!"
+ "Included file `lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!"
)
end
end
@@ -313,7 +313,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
expect(context.includes).to contain_exactly(
{ type: :local,
- location: '/local/file.yml',
+ location: 'local/file.yml',
blob: "http://localhost/#{project.full_path}/-/blob/#{sha}/local/file.yml",
raw: "http://localhost/#{project.full_path}/-/raw/#{sha}/local/file.yml",
extra: {},
@@ -334,14 +334,14 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
context_project: project.full_path,
context_sha: sha },
{ type: :file,
- location: '/templates/my-workflow.yml',
+ location: 'templates/my-workflow.yml',
blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-workflow.yml",
raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-workflow.yml",
extra: { project: another_project.full_path, ref: 'HEAD' },
context_project: project.full_path,
context_sha: sha },
{ type: :local,
- location: '/templates/my-build.yml',
+ location: 'templates/my-build.yml',
blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-build.yml",
raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-build.yml",
extra: {},
@@ -400,6 +400,44 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
end
end
+ describe 'include:component' do
+ let(:values) do
+ {
+ include: { component: "#{Gitlab.config.gitlab.host}/#{another_project.full_path}/component-x@master" },
+ image: 'image:1.0'
+ }
+ end
+
+ let(:other_project_files) do
+ {
+ '/component-x/template.yml' => <<~YAML
+ component_x_job:
+ script: echo Component X
+ YAML
+ }
+ end
+
+ before do
+ another_project.add_developer(user)
+ end
+
+ it 'appends the file to the values' do
+ output = processor.perform
+ expect(output.keys).to match_array([:image, :component_x_job])
+ end
+
+ context 'when feature flag ci_include_components is disabled' do
+ before do
+ stub_feature_flags(ci_include_components: false)
+ end
+
+ it 'returns an error' do
+ expect { processor.perform }
+ .to raise_error(described_class::IncludeError, /does not have a valid subkey for include./)
+ end
+ end
+ end
+
context 'when a valid project file is defined' do
let(:values) do
{
@@ -465,7 +503,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
expect(context.includes).to contain_exactly(
{ type: :file,
- location: '/templates/my-build.yml',
+ location: 'templates/my-build.yml',
blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-build.yml",
raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-build.yml",
extra: { project: another_project.full_path, ref: 'HEAD' },
@@ -474,7 +512,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel
{ type: :file,
blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-test.yml",
raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-test.yml",
- location: '/templates/my-test.yml',
+ location: 'templates/my-test.yml',
extra: { project: another_project.full_path, ref: 'HEAD' },
context_project: project.full_path,
context_sha: sha }
diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb
index e2bb55f3854..227b62d8ce8 100644
--- a/spec/lib/gitlab/ci/config/external/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Config::External::Rules do
+RSpec.describe Gitlab::Ci::Config::External::Rules, feature_category: :pipeline_authoring do
let(:rule_hashes) {}
subject(:rules) { described_class.new(rule_hashes) }
diff --git a/spec/lib/gitlab/ci/config/yaml_spec.rb b/spec/lib/gitlab/ci/config/yaml_spec.rb
new file mode 100644
index 00000000000..4b34553f55e
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/yaml_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Yaml, feature_category: :pipeline_authoring do
+ describe '.load!' do
+ it 'loads a single-doc YAML file' do
+ yaml = <<~YAML
+ image: 'image:1.0'
+ texts:
+ nested_key: 'value1'
+ more_text:
+ more_nested_key: 'value2'
+ YAML
+
+ config = described_class.load!(yaml)
+
+ expect(config).to eq({
+ image: 'image:1.0',
+ texts: {
+ nested_key: 'value1',
+ more_text: {
+ more_nested_key: 'value2'
+ }
+ }
+ })
+ end
+
+ it 'loads the first document from a multi-doc YAML file' do
+ yaml = <<~YAML
+ spec:
+ inputs:
+ test_input:
+ ---
+ image: 'image:1.0'
+ texts:
+ nested_key: 'value1'
+ more_text:
+ more_nested_key: 'value2'
+ YAML
+
+ config = described_class.load!(yaml)
+
+ expect(config).to eq({
+ spec: {
+ inputs: {
+ test_input: nil
+ }
+ }
+ })
+ end
+
+ context 'when ci_multi_doc_yaml is disabled' do
+ before do
+ stub_feature_flags(ci_multi_doc_yaml: false)
+ end
+
+ it 'loads a single-doc YAML file' do
+ yaml = <<~YAML
+ image: 'image:1.0'
+ texts:
+ nested_key: 'value1'
+ more_text:
+ more_nested_key: 'value2'
+ YAML
+
+ config = described_class.load!(yaml)
+
+ expect(config).to eq({
+ image: 'image:1.0',
+ texts: {
+ nested_key: 'value1',
+ more_text: {
+ more_nested_key: 'value2'
+ }
+ }
+ })
+ end
+
+ it 'loads the first document from a multi-doc YAML file' do
+ yaml = <<~YAML
+ spec:
+ inputs:
+ test_input:
+ ---
+ image: 'image:1.0'
+ texts:
+ nested_key: 'value1'
+ more_text:
+ more_nested_key: 'value2'
+ YAML
+
+ config = described_class.load!(yaml)
+
+ expect(config).to eq({
+ spec: {
+ inputs: {
+ test_input: nil
+ }
+ }
+ })
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 4b750cf3bcf..2c07e4d2224 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe Gitlab::Ci::CronParser do
end
end
- context 'when slash used' do
+ context 'when */ used' do
let(:cron) { '*/10 */6 */10 */10 *' }
let(:cron_timezone) { 'UTC' }
@@ -63,7 +63,7 @@ RSpec.describe Gitlab::Ci::CronParser do
end
end
- context 'when range and slash used' do
+ context 'when range and / are used' do
let(:cron) { '3-59/10 * * * *' }
let(:cron_timezone) { 'UTC' }
@@ -74,6 +74,17 @@ RSpec.describe Gitlab::Ci::CronParser do
end
end
+ context 'when / is used' do
+ let(:cron) { '3/10 * * * *' }
+ let(:cron_timezone) { 'UTC' }
+
+ it_behaves_like returns_time_for_epoch
+
+ it 'returns specific time' do
+ expect(subject.min).to be_in([3, 13, 23, 33, 43, 53])
+ end
+ end
+
context 'when cron_timezone is TZInfo format' do
before do
allow(Time).to receive(:zone)
diff --git a/spec/lib/gitlab/ci/interpolation/access_spec.rb b/spec/lib/gitlab/ci/interpolation/access_spec.rb
new file mode 100644
index 00000000000..9f6108a328d
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/access_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Access, feature_category: :pipeline_authoring do
+ subject { described_class.new(access, ctx) }
+
+ let(:access) do
+ 'inputs.data'
+ end
+
+ let(:ctx) do
+ { inputs: { data: 'abcd' }, env: { 'ENV' => 'dev' } }
+ end
+
+ it 'properly evaluates the access pattern' do
+ expect(subject.value).to eq 'abcd'
+ end
+
+ context 'when there are too many objects in the access path' do
+ let(:access) { 'a.b.c.d.e.f.g.h' }
+
+ it 'only support MAX_ACCESS_OBJECTS steps' do
+ expect(subject.objects.count).to eq 5
+ end
+ end
+
+ context 'when access expression size is too large' do
+ before do
+ stub_const("#{described_class}::MAX_ACCESS_BYTESIZE", 10)
+ end
+
+ it 'returns an error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first)
+ .to eq 'maximum interpolation expression size exceeded'
+ end
+ end
+
+ context 'when there are not enough objects in the access path' do
+ let(:access) { 'abc[123]' }
+
+ it 'returns an error when there are no objects found' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first)
+ .to eq 'invalid interpolation access pattern'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/block_spec.rb b/spec/lib/gitlab/ci/interpolation/block_spec.rb
new file mode 100644
index 00000000000..7f2be505d17
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/block_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_authoring do
+ subject { described_class.new(block, data, ctx) }
+
+ let(:data) do
+ 'inputs.data'
+ end
+
+ let(:block) do
+ "$[[ #{data} ]]"
+ end
+
+ let(:ctx) do
+ { inputs: { data: 'abc' }, env: { 'ENV' => 'dev' } }
+ end
+
+ it 'knows its content' do
+ expect(subject.content).to eq 'inputs.data'
+ end
+
+ it 'properly evaluates the access pattern' do
+ expect(subject.value).to eq 'abc'
+ end
+
+ describe '.match' do
+ it 'matches each block in a string' do
+ expect { |b| described_class.match('$[[ access1 ]] $[[ access2 ]]', &b) }
+ .to yield_successive_args(['$[[ access1 ]]', 'access1'], ['$[[ access2 ]]', 'access2'])
+ end
+
+ it 'matches an empty block' do
+ expect { |b| described_class.match('$[[]]', &b) }
+ .to yield_with_args('$[[]]', '')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/config_spec.rb b/spec/lib/gitlab/ci/interpolation/config_spec.rb
new file mode 100644
index 00000000000..e5987776e00
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/config_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Config, feature_category: :pipeline_authoring do
+ subject { described_class.new(YAML.safe_load(config)) }
+
+ let(:config) do
+ <<~CFG
+ test:
+ spec:
+ env: $[[ inputs.env ]]
+
+ $[[ inputs.key ]]:
+ name: $[[ inputs.key ]]
+ script: my-value
+ CFG
+ end
+
+ describe '#replace!' do
+ it 'replaces each od the nodes with a block return value' do
+ result = subject.replace! { |node| "abc#{node}cde" }
+
+ expect(result).to eq({
+ 'abctestcde' => { 'abcspeccde' => { 'abcenvcde' => 'abc$[[ inputs.env ]]cde' } },
+ 'abc$[[ inputs.key ]]cde' => {
+ 'abcnamecde' => 'abc$[[ inputs.key ]]cde',
+ 'abcscriptcde' => 'abcmy-valuecde'
+ }
+ })
+ end
+ end
+
+ context 'when config size is exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_NODES", 7)
+ end
+
+ it 'returns a config size error' do
+ replaced = 0
+
+ subject.replace! { replaced += 1 }
+
+ expect(replaced).to eq 4
+ expect(subject.errors.size).to eq 1
+ expect(subject.errors.first).to eq 'config too large'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/context_spec.rb b/spec/lib/gitlab/ci/interpolation/context_spec.rb
new file mode 100644
index 00000000000..ada896f4980
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/context_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Context, feature_category: :pipeline_authoring do
+ subject { described_class.new(ctx) }
+
+ let(:ctx) do
+ { inputs: { key: 'abc' } }
+ end
+
+ describe '#depth' do
+ it 'returns a max depth of the hash' do
+ expect(subject.depth).to eq 2
+ end
+ end
+
+ context 'when interpolation context is too complex' do
+ let(:ctx) do
+ { inputs: { key: { aaa: { bbb: 'ccc' } } } }
+ end
+
+ it 'raises an exception' do
+ expect { described_class.new(ctx) }
+ .to raise_error(described_class::ContextTooComplexError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/template_spec.rb b/spec/lib/gitlab/ci/interpolation/template_spec.rb
new file mode 100644
index 00000000000..8a243b4db05
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/template_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Template, feature_category: :pipeline_authoring do
+ subject { described_class.new(YAML.safe_load(config), ctx) }
+
+ let(:config) do
+ <<~CFG
+ test:
+ spec:
+ env: $[[ inputs.env ]]
+
+ $[[ inputs.key ]]:
+ name: $[[ inputs.key ]]
+ script: my-value
+ CFG
+ end
+
+ let(:ctx) do
+ { inputs: { env: 'dev', key: 'abc' } }
+ end
+
+ it 'collects interpolation blocks' do
+ expect(subject.size).to eq 2
+ end
+
+ it 'interpolates the values properly' do
+ expect(subject.interpolated).to eq YAML.safe_load <<~RESULT
+ test:
+ spec:
+ env: dev
+
+ abc:
+ name: abc
+ script: my-value
+ RESULT
+ end
+
+ context 'when interpolation can not be performed' do
+ let(:config) { '$[[ xxx.yyy ]]: abc' }
+
+ it 'does not interpolate the config' do
+ expect(subject).not_to be_valid
+ expect(subject.interpolated).to be_nil
+ 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 = { 'test' => ['a-abc-b', { 'c-abc-d' => 'd-abc-e', 'val' => 1 }] }
+
+ expect(subject).to be_valid
+ expect(subject.interpolated).to eq result
+ end
+ end
+
+ context 'when template contains symbols that need interpolation' do
+ subject do
+ described_class.new({ '$[[ inputs.key ]]'.to_sym => 'cde' }, ctx)
+ end
+
+ it 'performs a valid interpolation' do
+ expect(subject).to be_valid
+ expect(subject.interpolated).to eq({ 'abc' => 'cde' })
+ end
+ end
+
+ context 'when template is too large' do
+ before do
+ stub_const('Gitlab::Ci::Interpolation::Config::MAX_NODES', 1)
+ end
+
+ it 'returns an error' do
+ expect(subject.interpolated).to be_nil
+ expect(subject.errors.count).to eq 1
+ expect(subject.errors.first).to eq '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(subject.interpolated).to be_nil
+ expect(subject.errors.count).to eq 1
+ expect(subject.errors.first).to eq 'too many interpolation blocks'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb b/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb
index 30bcce21be2..6772c62ab93 100644
--- a/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb
@@ -8,14 +8,14 @@ RSpec.describe Gitlab::Ci::Parsers::Instrumentation do
Class.new do
prepend Gitlab::Ci::Parsers::Instrumentation
- def parse!(arg1, arg2)
+ def parse!(arg1, arg2:)
"parse #{arg1} #{arg2}"
end
end
end
it 'sets metrics for duration of parsing' do
- result = parser_class.new.parse!('hello', 'world')
+ result = parser_class.new.parse!('hello', arg2: 'world')
expect(result).to eq('parse hello world')
diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb
index 03cab021c17..5d2d22c04fc 100644
--- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb
@@ -203,24 +203,35 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end
context 'and name is not provided' do
- context 'when CVE identifier exists' do
- it 'combines identifier with location to create name' do
+ context 'when location does not exist' do
+ let(:location) { nil }
+
+ it 'returns only identifier name' do
finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
- expect(finding.name).to eq("CVE-2017-11429 in yarn.lock")
+ expect(finding.name).to eq("CVE-2017-11429")
end
end
- context 'when CWE identifier exists' do
- it 'combines identifier with location to create name' do
- finding = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
- expect(finding.name).to eq("CWE-2017-11429 in yarn.lock")
+ context 'when location exists' do
+ context 'when CVE identifier exists' do
+ it 'combines identifier with location to create name' do
+ finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
+ expect(finding.name).to eq("CVE-2017-11429 in yarn.lock")
+ end
+ end
+
+ context 'when CWE identifier exists' do
+ it 'combines identifier with location to create name' do
+ finding = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
+ expect(finding.name).to eq("CWE-2017-11429 in yarn.lock")
+ end
end
- end
- context 'when neither CVE nor CWE identifier exist' do
- it 'combines identifier with location to create name' do
- finding = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
- expect(finding.name).to eq("other-2017-11429 in yarn.lock")
+ context 'when neither CVE nor CWE identifier exist' do
+ it 'combines identifier with location to create name' do
+ finding = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
+ expect(finding.name).to eq("other-2017-11429 in yarn.lock")
+ end
end
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 16deeb6916f..31bffcbeb2a 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
@@ -2,208 +2,20 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines do
+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(:prev_pipeline) { create(:ci_pipeline, project: project) }
- let(:new_commit) { create(:commit, project: project) }
- let(:pipeline) { create(:ci_pipeline, project: project, sha: new_commit.sha) }
-
- let(:command) do
- Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
- end
-
- let(:step) { described_class.new(pipeline, command) }
-
- before do
- create(:ci_build, :interruptible, :running, pipeline: prev_pipeline)
- create(:ci_build, :interruptible, :success, pipeline: prev_pipeline)
- create(:ci_build, :created, pipeline: prev_pipeline)
-
- create(:ci_build, :interruptible, pipeline: pipeline)
- end
+ let_it_be(: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) }
describe '#perform!' do
subject(:perform) { step.perform! }
- before do
- expect(build_statuses(prev_pipeline)).to contain_exactly('running', 'success', 'created')
- expect(build_statuses(pipeline)).to contain_exactly('pending')
- end
-
- context 'when auto-cancel is enabled' do
- before do
- project.update!(auto_cancel_pending_pipelines: 'enabled')
- end
-
- it 'cancels only previous interruptible builds' do
- perform
-
- expect(build_statuses(prev_pipeline)).to contain_exactly('canceled', 'success', 'canceled')
- expect(build_statuses(pipeline)).to contain_exactly('pending')
- end
-
- it 'logs canceled pipelines' do
- allow(Gitlab::AppLogger).to receive(:info)
-
- perform
-
- expect(Gitlab::AppLogger).to have_received(:info).with(
- class: described_class.name,
- message: "Pipeline #{pipeline.id} auto-canceling pipeline #{prev_pipeline.id}",
- canceled_pipeline_id: prev_pipeline.id,
- canceled_by_pipeline_id: pipeline.id,
- canceled_by_pipeline_source: pipeline.source
- )
- end
-
- it 'cancels the builds with 2 queries to avoid query timeout' do
- second_query_regex = /WHERE "ci_pipelines"\."id" = \d+ AND \(NOT EXISTS/
- recorder = ActiveRecord::QueryRecorder.new { perform }
- second_query = recorder.occurrences.keys.filter { |occ| occ =~ second_query_regex }
-
- expect(second_query).to be_one
- end
-
- context 'when the previous pipeline has a child pipeline' do
- let(:child_pipeline) { create(:ci_pipeline, child_of: prev_pipeline) }
-
- context 'when the child pipeline has interruptible running jobs' do
- before do
- create(:ci_build, :interruptible, :running, pipeline: child_pipeline)
- create(:ci_build, :interruptible, :running, pipeline: child_pipeline)
- end
-
- it 'cancels all child pipeline builds' do
- expect(build_statuses(child_pipeline)).to contain_exactly('running', 'running')
-
- perform
-
- expect(build_statuses(child_pipeline)).to contain_exactly('canceled', 'canceled')
- end
-
- context 'when the child pipeline includes completed interruptible jobs' do
- before do
- create(:ci_build, :interruptible, :failed, pipeline: child_pipeline)
- create(:ci_build, :interruptible, :success, pipeline: child_pipeline)
- end
-
- it 'cancels all child pipeline builds with a cancelable_status' do
- expect(build_statuses(child_pipeline)).to contain_exactly('running', 'running', 'failed', 'success')
-
- perform
-
- expect(build_statuses(child_pipeline)).to contain_exactly('canceled', 'canceled', 'failed', 'success')
- end
- end
- end
-
- context 'when the child pipeline has started non-interruptible job' do
- before do
- create(:ci_build, :interruptible, :running, pipeline: child_pipeline)
- # non-interruptible started
- create(:ci_build, :success, pipeline: child_pipeline)
- end
+ it 'enqueues CancelRedundantPipelinesWorker' do
+ expect(Ci::CancelRedundantPipelinesWorker).to receive(:perform_async).with(pipeline.id)
- it 'does not cancel any child pipeline builds' do
- expect(build_statuses(child_pipeline)).to contain_exactly('running', 'success')
-
- perform
-
- expect(build_statuses(child_pipeline)).to contain_exactly('running', 'success')
- end
- end
-
- context 'when the child pipeline has non-interruptible non-started job' do
- before do
- create(:ci_build, :interruptible, :running, pipeline: child_pipeline)
- end
-
- not_started_statuses = Ci::HasStatus::AVAILABLE_STATUSES - Ci::HasStatus::STARTED_STATUSES
- context 'when the jobs are cancelable' do
- cancelable_not_started_statuses = Set.new(not_started_statuses).intersection(Ci::HasStatus::CANCELABLE_STATUSES)
- cancelable_not_started_statuses.each do |status|
- it "cancels all child pipeline builds when build status #{status} included" do
- # non-interruptible but non-started
- create(:ci_build, status.to_sym, pipeline: child_pipeline)
-
- expect(build_statuses(child_pipeline)).to contain_exactly('running', status)
-
- perform
-
- expect(build_statuses(child_pipeline)).to contain_exactly('canceled', 'canceled')
- end
- end
- end
-
- context 'when the jobs are not cancelable' do
- not_cancelable_not_started_statuses = not_started_statuses - Ci::HasStatus::CANCELABLE_STATUSES
- not_cancelable_not_started_statuses.each do |status|
- it "does not cancel child pipeline builds when build status #{status} included" do
- # non-interruptible but non-started
- create(:ci_build, status.to_sym, pipeline: child_pipeline)
-
- expect(build_statuses(child_pipeline)).to contain_exactly('running', status)
-
- perform
-
- expect(build_statuses(child_pipeline)).to contain_exactly('canceled', status)
- end
- end
- end
- end
- end
-
- context 'when the pipeline is a child pipeline' do
- let!(:parent_pipeline) { create(:ci_pipeline, project: project, sha: new_commit.sha) }
- let(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
-
- before do
- create(:ci_build, :interruptible, :running, pipeline: parent_pipeline)
- create(:ci_build, :interruptible, :running, pipeline: parent_pipeline)
- end
-
- it 'does not cancel any builds' do
- expect(build_statuses(prev_pipeline)).to contain_exactly('running', 'success', 'created')
- expect(build_statuses(parent_pipeline)).to contain_exactly('running', 'running')
-
- perform
-
- expect(build_statuses(prev_pipeline)).to contain_exactly('running', 'success', 'created')
- expect(build_statuses(parent_pipeline)).to contain_exactly('running', 'running')
- end
- end
-
- context 'when the previous pipeline source is webide' do
- let(:prev_pipeline) { create(:ci_pipeline, :webide, project: project) }
-
- it 'does not cancel builds of the previous pipeline' do
- perform
-
- expect(build_statuses(prev_pipeline)).to contain_exactly('created', 'running', 'success')
- expect(build_statuses(pipeline)).to contain_exactly('pending')
- end
- end
+ subject
end
-
- context 'when auto-cancel is disabled' do
- before do
- project.update!(auto_cancel_pending_pipelines: 'disabled')
- end
-
- it 'does not cancel any build' do
- subject
-
- expect(build_statuses(prev_pipeline)).to contain_exactly('running', 'success', 'created')
- expect(build_statuses(pipeline)).to contain_exactly('pending')
- end
- end
- end
-
- private
-
- def build_statuses(pipeline)
- pipeline.builds.pluck(:status)
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
deleted file mode 100644
index bec80a43a76..00000000000
--- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments, feature_category: :continuous_integration do
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:user) { create(:user) }
-
- let(:stage) { build(:ci_stage, project: project, statuses: [job]) }
- let(:pipeline) { create(:ci_pipeline, project: project, stages: [stage]) }
-
- let(:command) do
- Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
- end
-
- let(:step) { described_class.new(pipeline, command) }
-
- describe '#perform!' do
- subject { step.perform! }
-
- before do
- stub_feature_flags(move_create_deployments_to_worker: false)
- job.pipeline = pipeline
- end
-
- context 'when a pipeline contains a deployment job' do
- let!(:job) { build(:ci_build, :start_review_app, project: project) }
- let!(:environment) { create(:environment, project: project, name: job.expanded_environment_name) }
-
- it 'creates a deployment record' do
- expect { subject }.to change { Deployment.count }.by(1)
-
- job.reset
- expect(job.deployment.project).to eq(job.project)
- expect(job.deployment.ref).to eq(job.ref)
- expect(job.deployment.sha).to eq(job.sha)
- expect(job.deployment.deployable).to eq(job)
- expect(job.deployment.deployable_type).to eq('CommitStatus')
- expect(job.deployment.environment).to eq(job.persisted_environment)
- end
-
- context 'when the corresponding environment does not exist' do
- let!(:environment) {}
-
- it 'does not create a deployment record' do
- expect { subject }.not_to change { Deployment.count }
-
- expect(job.deployment).to be_nil
- end
- end
- end
-
- context 'when a pipeline contains a teardown job' do
- let!(:job) { build(:ci_build, :stop_review_app, project: project) }
- let!(:environment) { create(:environment, name: job.expanded_environment_name) }
-
- it 'does not create a deployment record' do
- expect { subject }.not_to change { Deployment.count }
-
- expect(job.deployment).to be_nil
- end
- end
-
- context 'when a pipeline does not contain a deployment job' do
- let!(:job) { build(:ci_build, project: project) }
-
- it 'does not create any deployments' do
- expect { subject }.not_to change { Deployment.count }
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/metrics_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/metrics_spec.rb
new file mode 100644
index 00000000000..b955d0e7cee
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/metrics_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Metrics, feature_category: :continuous_integration do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:pipeline) do
+ create(:ci_pipeline, project: project, ref: 'master', user: user, name: 'Build pipeline')
+ end
+
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ project: project,
+ current_user: user,
+ origin_ref: 'master')
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ subject(:run_chain) { step.perform! }
+
+ it 'does not break the chain' do
+ run_chain
+
+ expect(step.break?).to be false
+ end
+
+ context 'with pipeline name' do
+ it 'creates snowplow event' do
+ run_chain
+
+ expect_snowplow_event(
+ category: described_class.to_s,
+ action: 'create_pipeline_with_name',
+ project: pipeline.project,
+ user: pipeline.user,
+ namespace: pipeline.project.namespace
+ )
+ end
+ end
+
+ context 'without pipeline name' do
+ let_it_be(:pipeline) do
+ create(:ci_pipeline, project: project, ref: 'master', user: user)
+ end
+
+ it 'does not create snowplow event' do
+ run_chain
+
+ expect_no_snowplow_event
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 9373888aada..df18e1e4f48 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities, feature_category: :pipeline_execution do
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities, feature_category: :continuous_integration do
let(:project) { create(:project, :test_repo) }
let_it_be(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index 47f172922a5..1a622000c1b 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -1,11 +1,8 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
-require 'support/helpers/stubbed_feature'
-require 'support/helpers/stub_feature_flags'
-require_dependency 're2'
+require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches, feature_category: :continuous_integration do
include StubFeatureFlags
let(:left) { double('left') }
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
index 9e7ea3e4ea4..a60b00457fb 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -1,11 +1,8 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
-require 'support/helpers/stubbed_feature'
-require 'support/helpers/stub_feature_flags'
-require_dependency 're2'
+require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
+RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches, feature_category: :continuous_integration do
include StubFeatureFlags
let(:left) { double('left') }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 1f7f800e238..3043d7f5381 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -12,953 +12,860 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_au
let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage, when: 'on_success' } }
let(:previous_stages) { [] }
let(:current_stage) { instance_double(Gitlab::Ci::Pipeline::Seed::Stage, seeds_names: [attributes[:name]]) }
- let(:current_ci_stage) { build(:ci_stage, pipeline: pipeline) }
- let(:seed_build) { described_class.new(seed_context, attributes, previous_stages + [current_stage], current_ci_stage) }
+ let(:seed_build) { described_class.new(seed_context, attributes, previous_stages + [current_stage]) }
- shared_examples 'build seed' do
- describe '#attributes' do
- subject { seed_build.attributes }
+ describe '#attributes' do
+ subject { seed_build.attributes }
- it { is_expected.to be_a(Hash) }
- it { is_expected.to include(:name, :project, :ref) }
+ it { is_expected.to be_a(Hash) }
+ it { is_expected.to include(:name, :project, :ref) }
- context 'with job:when' do
- let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } }
+ context 'with job:when' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } }
- it { is_expected.to include(when: 'on_failure') }
+ it { is_expected.to include(when: 'on_failure') }
+ end
+
+ context 'with job:when:delayed' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', options: { start_in: '3 hours' } } }
+
+ it { is_expected.to include(when: 'delayed', options: { start_in: '3 hours' }) }
+ end
+
+ context 'with job:rules:[when:]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } }
+
+ it { is_expected.to include(when: 'always') }
end
- context 'with job:when:delayed' do
- let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', options: { start_in: '3 hours' } } }
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with job:rules:[when:delayed]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } }
it { is_expected.to include(when: 'delayed', options: { start_in: '3 hours' }) }
end
- context 'with job:rules:[when:]' do
- context 'is matched' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } }
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with job: rules but no explicit when:' do
+ let(:base_attributes) { { name: 'rspec', ref: 'master' } }
- it { is_expected.to include(when: 'always') }
+ context 'with a manual job' do
+ context 'with a matched rule' do
+ let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR == null' }]) }
+
+ it { is_expected.to include(when: 'manual') }
end
context 'is not matched' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } }
+ let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR != null' }]) }
it { is_expected.to include(when: 'never') }
end
end
- context 'with job:rules:[when:delayed]' do
+ context 'with an automatic job' do
context 'is matched' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } }
+ let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR == null' }]) }
- it { is_expected.to include(when: 'delayed', options: { start_in: '3 hours' }) }
+ it { is_expected.to include(when: 'on_success') }
end
context 'is not matched' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } }
+ let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR != null' }]) }
it { is_expected.to include(when: 'never') }
end
end
+ end
- context 'with job: rules but no explicit when:' do
- let(:base_attributes) { { name: 'rspec', ref: 'master' } }
-
- context 'with a manual job' do
- context 'with a matched rule' do
- let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR == null' }]) }
-
- it { is_expected.to include(when: 'manual') }
- end
-
- context 'is not matched' do
- let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR != null' }]) }
-
- it { is_expected.to include(when: 'never') }
- end
- end
+ context 'with job:rules:[variables:]' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ job_variables: [{ key: 'VAR1', value: 'var 1' },
+ { key: 'VAR2', value: 'var 2' }],
+ rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
+ end
- context 'with an automatic job' do
- context 'is matched' do
- let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR == null' }]) }
+ it do
+ is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' },
+ { key: 'VAR3', value: 'var 3' },
+ { key: 'VAR2', value: 'var 2' }])
+ end
+ end
- it { is_expected.to include(when: 'on_success') }
- end
+ context 'with job:tags' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ job_variables: [{ key: 'VARIABLE', value: 'value' }],
+ tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE']
+ }
+ end
- context 'is not matched' do
- let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR != null' }]) }
+ it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) }
+ it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) }
+ end
- it { is_expected.to include(when: 'never') }
- end
- end
+ context 'with cache:key' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: [{
+ key: 'a-value'
+ }]
+ }
end
- context 'with job:rules:[variables:]' do
+ it { is_expected.to include(options: { cache: [a_hash_including(key: 'a-value')] }) }
+
+ context 'with cache:key:files' do
let(:attributes) do
- { name: 'rspec',
+ {
+ name: 'rspec',
ref: 'master',
- job_variables: [{ key: 'VAR1', value: 'var 1' },
- { key: 'VAR2', value: 'var 2' }],
- rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
+ cache: [{
+ key: {
+ files: ['VERSION']
+ }
+ }]
+ }
end
- it do
- is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' },
- { key: 'VAR3', value: 'var 3' },
- { key: 'VAR2', value: 'var 2' }])
- end
+ it 'includes cache options' do
+ cache_options = {
+ options: {
+ cache: [a_hash_including(key: '0-f155568ad0933d8358f66b846133614f76dd0ca4')]
+ }
+ }
- it 'expects the same results on to_resource' do
- expect(seed_build.to_resource.yaml_variables).to include({ key: 'VAR1', value: 'new var 1' },
- { key: 'VAR3', value: 'var 3' },
- { key: 'VAR2', value: 'var 2' })
+ is_expected.to include(cache_options)
end
end
- context 'with job:tags' do
+ context 'with cache:key:prefix' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
- job_variables: [{ key: 'VARIABLE', value: 'value' }],
- tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE']
+ cache: [{
+ key: {
+ prefix: 'something'
+ }
+ }]
}
end
- it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) }
- it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) }
+ it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) }
end
- context 'with cache:key' do
+ context 'with cache:key:files and prefix' do
let(:attributes) do
{
name: 'rspec',
ref: 'master',
cache: [{
- key: 'a-value'
+ key: {
+ files: ['VERSION'],
+ prefix: 'something'
+ }
}]
}
end
- it { is_expected.to include(options: { cache: [a_hash_including(key: 'a-value')] }) }
-
- context 'with cache:key:files' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: [{
- key: {
- files: ['VERSION']
- }
- }]
- }
- end
-
- it 'includes cache options' do
- cache_options = {
- options: {
- cache: [a_hash_including(key: '0-f155568ad0933d8358f66b846133614f76dd0ca4')]
- }
+ it 'includes cache options' do
+ cache_options = {
+ options: {
+ cache: [a_hash_including(key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4')]
}
+ }
- is_expected.to include(cache_options)
- end
- end
-
- context 'with cache:key:prefix' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: [{
- key: {
- prefix: 'something'
- }
- }]
- }
- end
-
- it { is_expected.to include(options: { cache: [a_hash_including( key: 'something-default' )] }) }
+ is_expected.to include(cache_options)
end
+ end
+ end
- context 'with cache:key:files and prefix' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: [{
- key: {
- files: ['VERSION'],
- prefix: 'something'
- }
- }]
- }
- end
+ context 'with empty cache' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ cache: {}
+ }
+ end
- it 'includes cache options' do
- cache_options = {
- options: {
- cache: [a_hash_including(key: 'something-f155568ad0933d8358f66b846133614f76dd0ca4')]
- }
- }
+ it { is_expected.to include({}) }
+ end
- is_expected.to include(cache_options)
- end
- end
+ context 'with allow_failure' do
+ let(:options) do
+ { allow_failure_criteria: { exit_codes: [42] } }
end
- context 'with empty cache' do
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- cache: {}
- }
- end
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always' }]
+ end
- it { is_expected.to include({}) }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ ref: 'master',
+ options: options,
+ rules: rules
+ }
end
- context 'with allow_failure' do
- let(:options) do
- { allow_failure_criteria: { exit_codes: [42] } }
- end
+ context 'when rules does not override allow_failure' do
+ it { is_expected.to match a_hash_including(options: options) }
+ end
+ context 'when rules set allow_failure to true' do
let(:rules) do
- [{ if: '$VAR == null', when: 'always' }]
+ [{ if: '$VAR == null', when: 'always', allow_failure: true }]
end
- let(:attributes) do
- {
- name: 'rspec',
- ref: 'master',
- options: options,
- rules: rules
- }
- end
-
- context 'when rules does not override allow_failure' do
- it { is_expected.to match a_hash_including(options: options) }
- end
-
- context 'when rules set allow_failure to true' do
- let(:rules) do
- [{ if: '$VAR == null', when: 'always', allow_failure: true }]
- end
-
- it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
-
- context 'when options contain other static values' do
- let(:options) do
- { image: 'busybox', allow_failure_criteria: { exit_codes: [42] } }
- end
-
- it { is_expected.to match a_hash_including(options: { image: 'busybox', allow_failure_criteria: nil }) }
+ it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
+ end
- it 'deep merges options when exporting to_resource' do
- expect(seed_build.to_resource.options).to match a_hash_including(
- image: 'busybox', allow_failure_criteria: nil
- )
- end
- end
+ context 'when rules set allow_failure to false' do
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always', allow_failure: false }]
end
- context 'when rules set allow_failure to false' do
- let(:rules) do
- [{ if: '$VAR == null', when: 'always', allow_failure: false }]
- end
-
- it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
- end
+ it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
end
+ end
- context 'with workflow:rules:[variables:]' do
- let(:attributes) do
- { name: 'rspec',
- ref: 'master',
- yaml_variables: [{ key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }],
- job_variables: [{ key: 'VAR2', value: 'var 2' },
+ context 'with workflow:rules:[variables:]' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR2', value: 'var 2' },
{ key: 'VAR3', value: 'var 3' }],
- root_variables_inheritance: root_variables_inheritance }
- end
-
- context 'when the pipeline has variables' do
- let(:root_variables) do
- [{ key: 'VAR1', value: 'var overridden pipeline 1' },
- { key: 'VAR2', value: 'var pipeline 2' },
- { key: 'VAR3', value: 'var pipeline 3' },
- { key: 'VAR4', value: 'new var pipeline 4' }]
- end
-
- context 'when root_variables_inheritance is true' do
- let(:root_variables_inheritance) { true }
+ job_variables: [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }],
+ root_variables_inheritance: root_variables_inheritance }
+ end
- it 'returns calculated yaml variables' do
- expect(subject[:yaml_variables]).to match_array(
- [{ key: 'VAR1', value: 'var overridden pipeline 1' },
- { key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' },
- { key: 'VAR4', value: 'new var pipeline 4' }]
- )
- end
- end
+ context 'when the pipeline has variables' do
+ let(:root_variables) do
+ [{ key: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var pipeline 2' },
+ { key: 'VAR3', value: 'var pipeline 3' },
+ { key: 'VAR4', value: 'new var pipeline 4' }]
+ end
- context 'when root_variables_inheritance is false' do
- let(:root_variables_inheritance) { false }
+ context 'when root_variables_inheritance is true' do
+ let(:root_variables_inheritance) { true }
- it 'returns job variables' do
- expect(subject[:yaml_variables]).to match_array(
- [{ key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }]
- )
- end
+ it 'returns calculated yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' },
+ { key: 'VAR4', value: 'new var pipeline 4' }]
+ )
end
+ end
- context 'when root_variables_inheritance is an array' do
- let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) }
+ context 'when root_variables_inheritance is false' do
+ let(:root_variables_inheritance) { false }
- it 'returns calculated yaml variables' do
- expect(subject[:yaml_variables]).to match_array(
- [{ key: 'VAR1', value: 'var overridden pipeline 1' },
- { key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }]
- )
- end
+ it 'returns job variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }]
+ )
end
end
- context 'when the pipeline has not a variable' do
- let(:root_variables_inheritance) { true }
+ context 'when root_variables_inheritance is an array' do
+ let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) }
- it 'returns seed yaml variables' do
+ it 'returns calculated yaml variables' do
expect(subject[:yaml_variables]).to match_array(
- [{ key: 'VAR2', value: 'var 2' },
- { key: 'VAR3', value: 'var 3' }])
+ [{ key: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }]
+ )
end
end
end
- context 'when the job rule depends on variables' do
- let(:attributes) do
- { name: 'rspec',
- ref: 'master',
- yaml_variables: [{ key: 'VAR1', value: 'var 1' }],
- job_variables: [{ key: 'VAR1', value: 'var 1' }],
- root_variables_inheritance: root_variables_inheritance,
- rules: rules }
+ context 'when the pipeline has not a variable' do
+ let(:root_variables_inheritance) { true }
+
+ it 'returns seed yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }])
end
+ end
+ end
- let(:root_variables_inheritance) { true }
+ context 'when the job rule depends on variables' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR1', value: 'var 1' }],
+ job_variables: [{ key: 'VAR1', value: 'var 1' }],
+ root_variables_inheritance: root_variables_inheritance,
+ rules: rules }
+ end
- context 'when the rules use job variables' do
- let(:rules) do
- [{ if: '$VAR1 == "var 1"', variables: { VAR1: 'overridden var 1', VAR2: 'new var 2' } }]
- end
+ let(:root_variables_inheritance) { true }
- it 'recalculates the variables' do
- expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
- { key: 'VAR2', value: 'new var 2' })
- end
+ context 'when the rules use job variables' do
+ let(:rules) do
+ [{ if: '$VAR1 == "var 1"', variables: { VAR1: 'overridden var 1', VAR2: 'new var 2' } }]
end
- context 'when the rules use root variables' do
- let(:root_variables) do
- [{ key: 'VAR2', value: 'var pipeline 2' }]
- end
+ it 'recalculates the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
+ { key: 'VAR2', value: 'new var 2' })
+ end
+ end
- let(:rules) do
- [{ if: '$VAR2 == "var pipeline 2"', variables: { VAR1: 'overridden var 1', VAR2: 'overridden var 2' } }]
- end
+ context 'when the rules use root variables' do
+ let(:root_variables) do
+ [{ key: 'VAR2', value: 'var pipeline 2' }]
+ end
- it 'recalculates the variables' do
- expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
- { key: 'VAR2', value: 'overridden var 2' })
- end
+ let(:rules) do
+ [{ if: '$VAR2 == "var pipeline 2"', variables: { VAR1: 'overridden var 1', VAR2: 'overridden var 2' } }]
+ end
- context 'when the root_variables_inheritance is false' do
- let(:root_variables_inheritance) { false }
+ it 'recalculates the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
+ { key: 'VAR2', value: 'overridden var 2' })
+ end
- it 'does not recalculate the variables' do
- expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' })
- end
+ context 'when the root_variables_inheritance is false' do
+ let(:root_variables_inheritance) { false }
+
+ it 'does not recalculate the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' })
end
end
end
end
+ end
+
+ describe '#bridge?' do
+ subject { seed_build.bridge? }
+
+ context 'when job is a downstream bridge' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
+ end
- describe '#bridge?' do
- subject { seed_build.bridge? }
+ it { is_expected.to be_truthy }
- context 'when job is a downstream bridge' do
+ context 'when trigger definition is empty' do
let(:attributes) do
- { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
+ { name: 'rspec', ref: 'master', options: { trigger: '' } }
end
- it { is_expected.to be_truthy }
-
- context 'when trigger definition is empty' do
- let(:attributes) do
- { name: 'rspec', ref: 'master', options: { trigger: '' } }
- end
+ it { is_expected.to be_falsey }
+ end
+ end
- it { is_expected.to be_falsey }
- end
+ context 'when job is an upstream bridge' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } }
end
- context 'when job is an upstream bridge' do
+ it { is_expected.to be_truthy }
+
+ context 'when upstream definition is empty' do
let(:attributes) do
- { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } }
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } }
end
- it { is_expected.to be_truthy }
+ it { is_expected.to be_falsey }
+ end
+ end
- context 'when upstream definition is empty' do
- let(:attributes) do
- { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } }
- end
+ context 'when job is not a bridge' do
+ it { is_expected.to be_falsey }
+ end
+ end
- it { is_expected.to be_falsey }
- end
- end
+ describe '#to_resource' do
+ subject { seed_build.to_resource }
- context 'when job is not a bridge' do
- it { is_expected.to be_falsey }
- end
+ it 'memoizes a resource object' do
+ expect(subject.object_id).to eq seed_build.to_resource.object_id
end
- describe '#to_resource' do
- subject { seed_build.to_resource }
+ it 'can not be persisted without explicit assignment' do
+ pipeline.save!
- it 'memoizes a resource object' do
- expect(subject.object_id).to eq seed_build.to_resource.object_id
- end
+ expect(subject).not_to be_persisted
+ end
+ end
- it 'can not be persisted without explicit assignment' do
- pipeline.save!
+ describe 'applying job inclusion policies' do
+ subject { seed_build }
- expect(subject).not_to be_persisted
+ context 'when no branch policy is specified' do
+ let(:attributes) do
+ { name: 'rspec' }
end
- end
- describe 'applying job inclusion policies' do
- subject { seed_build }
+ it { is_expected.to be_included }
+ end
- context 'when no branch policy is specified' do
+ context 'when branch policy does not match' do
+ context 'when using only' do
let(:attributes) do
- { name: 'rspec' }
+ { name: 'rspec', only: { refs: ['deploy'] } }
end
- it { is_expected.to be_included }
+ it { is_expected.not_to be_included }
end
- context 'when branch policy does not match' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: ['deploy'] } }
- end
-
- it { is_expected.not_to be_included }
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: ['deploy'] } }
end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: ['deploy'] } }
- end
+ it { is_expected.to be_included }
+ end
- it { is_expected.to be_included }
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy] },
+ except: { refs: %w[deploy] }
+ }
end
- context 'with both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[deploy] },
- except: { refs: %w[deploy] }
- }
- end
-
- it { is_expected.not_to be_included }
- end
+ it { is_expected.not_to be_included }
end
+ end
- context 'when branch regexp policy does not match' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[/^deploy$/] } }
- end
-
- it { is_expected.not_to be_included }
+ context 'when branch regexp policy does not match' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[/^deploy$/] } }
end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[/^deploy$/] } }
- end
+ it { is_expected.not_to be_included }
+ end
- it { is_expected.to be_included }
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[/^deploy$/] } }
end
- context 'with both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[/^deploy$/] },
- except: { refs: %w[/^deploy$/] }
- }
- end
+ it { is_expected.to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[/^deploy$/] },
+ except: { refs: %w[/^deploy$/] }
+ }
end
- end
- context 'when branch policy matches' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[deploy master] } }
- end
+ it { is_expected.not_to be_included }
+ end
+ end
- it { is_expected.to be_included }
+ context 'when branch policy matches' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[deploy master] } }
end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[deploy master] } }
- end
+ it { is_expected.to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[deploy master] } }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[deploy master] },
- except: { refs: %w[deploy master] }
- }
- end
+ it { is_expected.not_to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy master] },
+ except: { refs: %w[deploy master] }
+ }
end
- end
- context 'when keyword policy matches' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[branches] } }
- end
+ it { is_expected.not_to be_included }
+ end
+ end
- it { is_expected.to be_included }
+ context 'when keyword policy matches' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[branches] } }
end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[branches] } }
- end
+ it { is_expected.to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[branches] } }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[branches] },
- except: { refs: %w[branches] }
- }
- end
+ it { is_expected.not_to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches] },
+ except: { refs: %w[branches] }
+ }
end
- end
- context 'when keyword policy does not match' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[tags] } }
- end
+ it { is_expected.not_to be_included }
+ end
+ end
- it { is_expected.not_to be_included }
+ context 'when keyword policy does not match' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[tags] } }
end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[tags] } }
- end
+ it { is_expected.not_to be_included }
+ end
- it { is_expected.to be_included }
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[tags] } }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[tags] },
- except: { refs: %w[tags] }
- }
- end
+ it { is_expected.to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[tags] },
+ except: { refs: %w[tags] }
+ }
end
- end
- context 'with source-keyword policy' do
- using RSpec::Parameterized
+ it { is_expected.not_to be_included }
+ end
+ end
- let(:pipeline) do
- build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source, project: project)
- end
+ context 'with source-keyword policy' do
+ using RSpec::Parameterized
- context 'matches' do
- where(:keyword, :source) do
- [
- %w[pushes push],
- %w[web web],
- %w[triggers trigger],
- %w[schedules schedule],
- %w[api api],
- %w[external external]
- ]
- end
+ let(:pipeline) do
+ build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source, project: project)
+ end
- with_them do
- context 'using an only policy' do
- let(:attributes) do
- { name: 'rspec', only: { refs: [keyword] } }
- end
+ context 'matches' do
+ where(:keyword, :source) do
+ [
+ %w[pushes push],
+ %w[web web],
+ %w[triggers trigger],
+ %w[schedules schedule],
+ %w[api api],
+ %w[external external]
+ ]
+ end
- it { is_expected.to be_included }
+ with_them do
+ context 'using an only policy' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
end
- context 'using an except policy' do
- let(:attributes) do
- { name: 'rspec', except: { refs: [keyword] } }
- end
+ it { is_expected.to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'using an except policy' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
end
- context 'using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: [keyword] },
- except: { refs: [keyword] }
- }
- end
+ it { is_expected.not_to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
end
- end
- end
- context 'non-matches' do
- where(:keyword, :source) do
- %w[web trigger schedule api external].map { |source| ['pushes', source] } +
- %w[push trigger schedule api external].map { |source| ['web', source] } +
- %w[push web schedule api external].map { |source| ['triggers', source] } +
- %w[push web trigger api external].map { |source| ['schedules', source] } +
- %w[push web trigger schedule external].map { |source| ['api', source] } +
- %w[push web trigger schedule api].map { |source| ['external', source] }
+ it { is_expected.not_to be_included }
end
+ end
+ end
- with_them do
- context 'using an only policy' do
- let(:attributes) do
- { name: 'rspec', only: { refs: [keyword] } }
- end
+ context 'non-matches' do
+ where(:keyword, :source) do
+ %w[web trigger schedule api external].map { |source| ['pushes', source] } +
+ %w[push trigger schedule api external].map { |source| ['web', source] } +
+ %w[push web schedule api external].map { |source| ['triggers', source] } +
+ %w[push web trigger api external].map { |source| ['schedules', source] } +
+ %w[push web trigger schedule external].map { |source| ['api', source] } +
+ %w[push web trigger schedule api].map { |source| ['external', source] }
+ end
- it { is_expected.not_to be_included }
+ with_them do
+ context 'using an only policy' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
end
- context 'using an except policy' do
- let(:attributes) do
- { name: 'rspec', except: { refs: [keyword] } }
- end
+ it { is_expected.not_to be_included }
+ end
- it { is_expected.to be_included }
+ context 'using an except policy' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
end
- context 'using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: [keyword] },
- except: { refs: [keyword] }
- }
- end
+ it { is_expected.to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
end
+
+ it { is_expected.not_to be_included }
end
end
end
+ end
- context 'when repository path matches' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: ["branches@#{pipeline.project_full_path}"] } }
- end
-
- it { is_expected.to be_included }
+ context 'when repository path matches' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: ["branches@#{pipeline.project_full_path}"] } }
end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: ["branches@#{pipeline.project_full_path}"] } }
- end
+ it { is_expected.to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: ["branches@#{pipeline.project_full_path}"] } }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: ["branches@#{pipeline.project_full_path}"] },
- except: { refs: ["branches@#{pipeline.project_full_path}"] }
- }
- end
+ it { is_expected.not_to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: ["branches@#{pipeline.project_full_path}"] },
+ except: { refs: ["branches@#{pipeline.project_full_path}"] }
+ }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: {
- refs: ["branches@#{pipeline.project_full_path}"]
- },
- except: {
- refs: ["branches@#{pipeline.project_full_path}"]
- }
- }
- end
-
- it { is_expected.not_to be_included }
- end
+ it { is_expected.not_to be_included }
end
- context 'when repository path does not match' do
- context 'when using only' do
- let(:attributes) do
- { name: 'rspec', only: { refs: %w[branches@fork] } }
- end
-
- it { is_expected.not_to be_included }
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: {
+ refs: ["branches@#{pipeline.project_full_path}"]
+ },
+ except: {
+ refs: ["branches@#{pipeline.project_full_path}"]
+ }
+ }
end
- context 'when using except' do
- let(:attributes) do
- { name: 'rspec', except: { refs: %w[branches@fork] } }
- end
+ it { is_expected.not_to be_included }
+ end
+ end
- it { is_expected.to be_included }
+ context 'when repository path does not match' do
+ context 'when using only' do
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[branches@fork] } }
end
- context 'when using both only and except policies' do
- let(:attributes) do
- {
- name: 'rspec',
- only: { refs: %w[branches@fork] },
- except: { refs: %w[branches@fork] }
- }
- end
+ it { is_expected.not_to be_included }
+ end
- it { is_expected.not_to be_included }
+ context 'when using except' do
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[branches@fork] } }
end
+
+ it { is_expected.to be_included }
end
- context 'using rules:' do
- using RSpec::Parameterized
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches@fork] },
+ except: { refs: %w[branches@fork] }
+ }
+ end
- let(:attributes) { { name: 'rspec', rules: rule_set, when: 'on_success' } }
+ it { is_expected.not_to be_included }
+ end
+ end
- context 'with a matching if: rule' do
- context 'with an explicit `when: never`' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE == null', when: 'never' }]],
- [[{ if: '$VARIABLE == null', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]],
- [[{ if: '$VARIABLE != "the wrong value"', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]]
- ]
- end
+ context 'using rules:' do
+ using RSpec::Parameterized
- with_them do
- it { is_expected.not_to be_included }
+ let(:attributes) { { name: 'rspec', rules: rule_set, when: 'on_success' } }
- it 'still correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'never')
- end
- end
+ context 'with a matching if: rule' do
+ context 'with an explicit `when: never`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE == null', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]]
+ ]
end
- context 'with an explicit `when: always`' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE == null', when: 'always' }]],
- [[{ if: '$VARIABLE == null', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]],
- [[{ if: '$VARIABLE != "the wrong value"', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]]
- ]
- end
-
- with_them do
- it { is_expected.to be_included }
+ with_them do
+ it { is_expected.not_to be_included }
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'always')
- end
+ it 'still correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
end
end
+ end
- context 'with an explicit `when: on_failure`' do
- where(:rule_set) do
- [
- [[{ if: '$CI_JOB_NAME == "rspec" && $VAR == null', when: 'on_failure' }]],
- [[{ if: '$VARIABLE != null', when: 'delayed', start_in: '1 day' }, { if: '$CI_JOB_NAME == "rspec"', when: 'on_failure' }]],
- [[{ if: '$VARIABLE == "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$CI_BUILD_NAME == "rspec"', when: 'on_failure' }]]
- ]
- end
-
- with_them do
- it { is_expected.to be_included }
-
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'on_failure')
- end
- end
+ context 'with an explicit `when: always`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'always' }]],
+ [[{ if: '$VARIABLE == null', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]]
+ ]
end
- context 'with an explicit `when: delayed`' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }]],
- [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]],
- [[{ if: '$VARIABLE != "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]]
- ]
- end
-
- with_them do
- it { is_expected.to be_included }
+ with_them do
+ it { is_expected.to be_included }
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'delayed', options: { start_in: '1 day' })
- end
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'always')
end
end
+ end
- context 'without an explicit when: value' do
- where(:rule_set) do
- [
- [[{ if: '$VARIABLE == null' }]],
- [[{ if: '$VARIABLE == null' }, { if: '$VARIABLE == null' }]],
- [[{ if: '$VARIABLE != "the wrong value"' }, { if: '$VARIABLE == null' }]]
- ]
- end
+ context 'with an explicit `when: on_failure`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$CI_JOB_NAME == "rspec" && $VAR == null', when: 'on_failure' }]],
+ [[{ if: '$VARIABLE != null', when: 'delayed', start_in: '1 day' }, { if: '$CI_JOB_NAME == "rspec"', when: 'on_failure' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$CI_BUILD_NAME == "rspec"', when: 'on_failure' }]]
+ ]
+ end
- with_them do
- it { is_expected.to be_included }
+ with_them do
+ it { is_expected.to be_included }
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'on_success')
- end
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_failure')
end
end
end
- context 'with a matching changes: rule' do
- let(:pipeline) do
- build(:ci_pipeline, project: project).tap do |pipeline|
- stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml])
- end
+ context 'with an explicit `when: delayed`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }]],
+ [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]]
+ ]
end
- context 'with an explicit `when: never`' do
- where(:rule_set) do
- [
- [[{ changes: { paths: %w[*/**/*.rb] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb] }, when: 'always' }]],
- [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }]],
- [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'never' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'always' }]],
- [[{ changes: { paths: %w[*.yml] }, when: 'never' }, { changes: { paths: %w[*.yml] }, when: 'always' }]],
- [[{ changes: { paths: %w[.*.yml] }, when: 'never' }, { changes: { paths: %w[.*.yml] }, when: 'always' }]],
- [[{ changes: { paths: %w[**/*] }, when: 'never' }, { changes: { paths: %w[**/*] }, when: 'always' }]],
- [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }]],
- [[{ changes: { paths: %w[.*.yml **/*] }, when: 'never' }, { changes: { paths: %w[.*.yml **/*] }, when: 'always' }]]
- ]
- end
-
- with_them do
- it { is_expected.not_to be_included }
+ with_them do
+ it { is_expected.to be_included }
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'never')
- end
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'delayed', options: { start_in: '1 day' })
end
end
+ end
- context 'with an explicit `when: always`' do
- where(:rule_set) do
- [
- [[{ changes: { paths: %w[*/**/*.rb] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb] }, when: 'never' }]],
- [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }]],
- [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'always' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'never' }]],
- [[{ changes: { paths: %w[*.yml] }, when: 'always' }, { changes: { paths: %w[*.yml] }, when: 'never' }]],
- [[{ changes: { paths: %w[.*.yml] }, when: 'always' }, { changes: { paths: %w[.*.yml] }, when: 'never' }]],
- [[{ changes: { paths: %w[**/*] }, when: 'always' }, { changes: { paths: %w[**/*] }, when: 'never' }]],
- [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }]],
- [[{ changes: { paths: %w[.*.yml **/*] }, when: 'always' }, { changes: { paths: %w[.*.yml **/*] }, when: 'never' }]]
- ]
- end
+ context 'without an explicit when: value' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null' }]],
+ [[{ if: '$VARIABLE == null' }, { if: '$VARIABLE == null' }]],
+ [[{ if: '$VARIABLE != "the wrong value"' }, { if: '$VARIABLE == null' }]]
+ ]
+ end
- with_them do
- it { is_expected.to be_included }
+ with_them do
+ it { is_expected.to be_included }
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'always')
- end
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_success')
end
end
+ end
+ end
- context 'without an explicit when: value' do
- where(:rule_set) do
- [
- [[{ changes: { paths: %w[*/**/*.rb] } }]],
- [[{ changes: { paths: %w[app/models/ci/pipeline.rb] } }]],
- [[{ changes: { paths: %w[spec/**/*.rb] } }]],
- [[{ changes: { paths: %w[*.yml] } }]],
- [[{ changes: { paths: %w[.*.yml] } }]],
- [[{ changes: { paths: %w[**/*] } }]],
- [[{ changes: { paths: %w[*/**/*.rb *.yml] } }]],
- [[{ changes: { paths: %w[.*.yml **/*] } }]]
- ]
- end
-
- with_them do
- it { is_expected.to be_included }
-
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'on_success')
- end
- end
+ context 'with a matching changes: rule' do
+ let(:pipeline) do
+ build(:ci_pipeline, project: project).tap do |pipeline|
+ stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml])
end
end
- context 'with no matching rule' do
+ context 'with an explicit `when: never`' do
where(:rule_set) do
[
- [[{ if: '$VARIABLE != null', when: 'never' }]],
- [[{ if: '$VARIABLE != null', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
- [[{ if: '$VARIABLE == "the wrong value"', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
- [[{ if: '$VARIABLE != null', when: 'always' }]],
- [[{ if: '$VARIABLE != null', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
- [[{ if: '$VARIABLE == "the wrong value"', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
- [[{ if: '$VARIABLE != null' }]],
- [[{ if: '$VARIABLE != null' }, { if: '$VARIABLE != null' }]],
- [[{ if: '$VARIABLE == "the wrong value"' }, { if: '$VARIABLE != null' }]]
+ [[{ changes: { paths: %w[*/**/*.rb] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'never' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'always' }]],
+ [[{ changes: { paths: %w[*.yml] }, when: 'never' }, { changes: { paths: %w[*.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[.*.yml] }, when: 'never' }, { changes: { paths: %w[.*.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[**/*] }, when: 'never' }, { changes: { paths: %w[**/*] }, when: 'always' }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }]],
+ [[{ changes: { paths: %w[.*.yml **/*] }, when: 'never' }, { changes: { paths: %w[.*.yml **/*] }, when: 'always' }]]
]
end
@@ -971,249 +878,291 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_au
end
end
- context 'with a rule using CI_ENVIRONMENT_NAME variable' do
- let(:rule_set) do
- [{ if: '$CI_ENVIRONMENT_NAME == "test"' }]
+ context 'with an explicit `when: always`' do
+ where(:rule_set) do
+ [
+ [[{ changes: { paths: %w[*/**/*.rb] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'always' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'never' }]],
+ [[{ changes: { paths: %w[*.yml] }, when: 'always' }, { changes: { paths: %w[*.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[.*.yml] }, when: 'always' }, { changes: { paths: %w[.*.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[**/*] }, when: 'always' }, { changes: { paths: %w[**/*] }, when: 'never' }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }]],
+ [[{ changes: { paths: %w[.*.yml **/*] }, when: 'always' }, { changes: { paths: %w[.*.yml **/*] }, when: 'never' }]]
+ ]
end
- context 'when environment:name satisfies the rule' do
- let(:attributes) { { name: 'rspec', rules: rule_set, environment: 'test', when: 'on_success' } }
-
+ with_them do
it { is_expected.to be_included }
it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'on_success')
+ expect(seed_build.attributes).to include(when: 'always')
end
end
+ end
- context 'when environment:name does not satisfy rule' do
- let(:attributes) { { name: 'rspec', rules: rule_set, environment: 'dev', when: 'on_success' } }
-
- it { is_expected.not_to be_included }
-
- it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'never')
- end
+ context 'without an explicit when: value' do
+ where(:rule_set) do
+ [
+ [[{ changes: { paths: %w[*/**/*.rb] } }]],
+ [[{ changes: { paths: %w[app/models/ci/pipeline.rb] } }]],
+ [[{ changes: { paths: %w[spec/**/*.rb] } }]],
+ [[{ changes: { paths: %w[*.yml] } }]],
+ [[{ changes: { paths: %w[.*.yml] } }]],
+ [[{ changes: { paths: %w[**/*] } }]],
+ [[{ changes: { paths: %w[*/**/*.rb *.yml] } }]],
+ [[{ changes: { paths: %w[.*.yml **/*] } }]]
+ ]
end
- context 'when environment:name is not set' do
- it { is_expected.not_to be_included }
+ with_them do
+ it { is_expected.to be_included }
it 'correctly populates when:' do
- expect(seed_build.attributes).to include(when: 'never')
+ expect(seed_build.attributes).to include(when: 'on_success')
end
end
end
+ end
- context 'with no rules' do
- let(:rule_set) { [] }
+ context 'with no matching rule' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE != null', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE != null', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE != null' }]],
+ [[{ if: '$VARIABLE != null' }, { if: '$VARIABLE != null' }]],
+ [[{ if: '$VARIABLE == "the wrong value"' }, { if: '$VARIABLE != null' }]]
+ ]
+ end
+ with_them do
it { is_expected.not_to be_included }
it 'correctly populates when:' do
expect(seed_build.attributes).to include(when: 'never')
end
end
+ end
+
+ context 'with a rule using CI_ENVIRONMENT_NAME variable' do
+ let(:rule_set) do
+ [{ if: '$CI_ENVIRONMENT_NAME == "test"' }]
+ end
- context 'with invalid rules raising error' do
- let(:rule_set) do
- [
- { changes: { paths: ['README.md'], compare_to: 'invalid-ref' }, when: 'never' }
- ]
+ context 'when environment:name satisfies the rule' do
+ let(:attributes) { { name: 'rspec', rules: rule_set, environment: 'test', when: 'on_success' } }
+
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_success')
end
+ end
+
+ context 'when environment:name does not satisfy rule' do
+ let(:attributes) { { name: 'rspec', rules: rule_set, environment: 'dev', when: 'on_success' } }
it { is_expected.not_to be_included }
it 'correctly populates when:' do
expect(seed_build.attributes).to include(when: 'never')
end
+ end
- it 'returns an error' do
- expect(seed_build.errors).to contain_exactly(
- 'Failed to parse rule for rspec: rules:changes:compare_to is not a valid ref'
- )
+ context 'when environment:name is not set' do
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
end
end
end
- end
-
- describe 'applying needs: dependency' do
- subject { seed_build }
- let(:needs_count) { 1 }
+ context 'with no rules' do
+ let(:rule_set) { [] }
- let(:needs_attributes) do
- Array.new(needs_count, name: 'build')
- end
+ it { is_expected.not_to be_included }
- let(:attributes) do
- {
- name: 'rspec',
- needs_attributes: needs_attributes
- }
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
end
- context 'when build job is not present in prior stages' do
- it "is included" do
- is_expected.to be_included
+ context 'with invalid rules raising error' do
+ let(:rule_set) do
+ [
+ { changes: { paths: ['README.md'], compare_to: 'invalid-ref' }, when: 'never' }
+ ]
end
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- "'rspec' job needs 'build' job, but 'build' is not in any previous stage")
- end
+ it { is_expected.not_to be_included }
- context 'when the needed job is optional' do
- let(:needs_attributes) { [{ name: 'build', optional: true }] }
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
- it "does not return an error" do
- expect(subject.errors).to be_empty
- end
+ it 'returns an error' do
+ expect(seed_build.errors).to contain_exactly(
+ 'Failed to parse rule for rspec: rules:changes:compare_to is not a valid ref'
+ )
end
end
+ end
+ end
- context 'when build job is part of prior stages' do
- let(:stage_attributes) do
- {
- name: 'build',
- index: 0,
- builds: [{ name: 'build' }]
- }
- end
+ describe 'applying needs: dependency' do
+ subject { seed_build }
- let(:stage_seed) do
- Gitlab::Ci::Pipeline::Seed::Stage.new(seed_context, stage_attributes, [])
- end
+ let(:needs_count) { 1 }
- let(:previous_stages) { [stage_seed] }
+ let(:needs_attributes) do
+ Array.new(needs_count, name: 'build')
+ end
- it "is included" do
- is_expected.to be_included
- end
+ let(:attributes) do
+ {
+ name: 'rspec',
+ needs_attributes: needs_attributes
+ }
+ end
- it "does not have errors" do
- expect(subject.errors).to be_empty
- end
+ context 'when build job is not present in prior stages' do
+ it "is included" do
+ is_expected.to be_included
end
- context 'when build job is part of the same stage' do
- let(:current_stage) { double(seeds_names: [attributes[:name], 'build']) }
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "'rspec' job needs 'build' job, but 'build' is not in any previous stage")
+ end
- it 'is included' do
- is_expected.to be_included
- end
+ context 'when the needed job is optional' do
+ let(:needs_attributes) { [{ name: 'build', optional: true }] }
- it 'does not have errors' do
+ it "does not return an error" do
expect(subject.errors).to be_empty
end
end
+ end
- context 'when using 101 needs' do
- let(:needs_count) { 101 }
-
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- "rspec: one job can only need 50 others, but you have listed 101. See needs keyword documentation for more details")
- end
+ context 'when build job is part of prior stages' do
+ let(:stage_attributes) do
+ {
+ name: 'build',
+ index: 0,
+ builds: [{ name: 'build' }]
+ }
+ end
- context 'when ci_needs_size_limit is set to 100' do
- before do
- project.actual_limits.update!(ci_needs_size_limit: 100)
- end
+ let(:stage_seed) do
+ Gitlab::Ci::Pipeline::Seed::Stage.new(seed_context, stage_attributes, [])
+ end
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- "rspec: one job can only need 100 others, but you have listed 101. See needs keyword documentation for more details")
- end
- end
+ let(:previous_stages) { [stage_seed] }
- context 'when ci_needs_size_limit is set to 0' do
- before do
- project.actual_limits.update!(ci_needs_size_limit: 0)
- end
+ it "is included" do
+ is_expected.to be_included
+ end
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- "rspec: one job can only need 0 others, but you have listed 101. See needs keyword documentation for more details")
- end
- end
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
end
end
- describe 'applying pipeline variables' do
- subject { seed_build }
+ context 'when build job is part of the same stage' do
+ let(:current_stage) { double(seeds_names: [attributes[:name], 'build']) }
- let(:pipeline_variables) { [] }
- let(:pipeline) do
- build(:ci_empty_pipeline, project: project, sha: head_sha, variables: pipeline_variables)
+ it 'is included' do
+ is_expected.to be_included
end
- context 'containing variable references' do
- let(:pipeline_variables) do
- [
- build(:ci_pipeline_variable, key: 'A', value: '$B'),
- build(:ci_pipeline_variable, key: 'B', value: '$C')
- ]
- end
+ it 'does not have errors' do
+ expect(subject.errors).to be_empty
+ end
+ end
- it "does not have errors" do
- expect(subject.errors).to be_empty
- end
+ context 'when using 101 needs' do
+ let(:needs_count) { 101 }
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 50 others, but you have listed 101. See needs keyword documentation for more details")
end
- context 'containing cyclic reference' do
- let(:pipeline_variables) do
- [
- build(:ci_pipeline_variable, key: 'A', value: '$B'),
- build(:ci_pipeline_variable, key: 'B', value: '$C'),
- build(:ci_pipeline_variable, key: 'C', value: '$A')
- ]
+ context 'when ci_needs_size_limit is set to 100' do
+ before do
+ project.actual_limits.update!(ci_needs_size_limit: 100)
end
it "returns an error" do
expect(subject.errors).to contain_exactly(
- 'rspec: circular variable reference detected: ["A", "B", "C"]')
+ "rspec: one job can only need 100 others, but you have listed 101. See needs keyword documentation for more details")
end
+ end
- context 'with job:rules:[if:]' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$C != null', when: 'always' }] } }
-
- it "included? does not raise" do
- expect { subject.included? }.not_to raise_error
- end
+ context 'when ci_needs_size_limit is set to 0' do
+ before do
+ project.actual_limits.update!(ci_needs_size_limit: 0)
+ end
- it "included? returns true" do
- expect(subject.included?).to eq(true)
- end
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 0 others, but you have listed 101. See needs keyword documentation for more details")
end
end
end
end
- describe 'feature flag ci_reuse_build_in_seed_context' do
- let(:attributes) do
- { name: 'rspec', rules: [{ if: '$VARIABLE == null' }], when: 'on_success' }
+ describe 'applying pipeline variables' do
+ subject { seed_build }
+
+ let(:pipeline_variables) { [] }
+ let(:pipeline) do
+ build(:ci_empty_pipeline, project: project, sha: head_sha, variables: pipeline_variables)
end
- context 'when enabled' do
- it_behaves_like 'build seed'
+ context 'containing variable references' do
+ let(:pipeline_variables) do
+ [
+ build(:ci_pipeline_variable, key: 'A', value: '$B'),
+ build(:ci_pipeline_variable, key: 'B', value: '$C')
+ ]
+ end
- it 'initializes the build once' do
- expect(Ci::Build).to receive(:new).once.and_call_original
- seed_build.to_resource
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
end
end
- context 'when disabled' do
- before do
- stub_feature_flags(ci_reuse_build_in_seed_context: false)
+ context 'containing cyclic reference' do
+ let(:pipeline_variables) do
+ [
+ build(:ci_pipeline_variable, key: 'A', value: '$B'),
+ build(:ci_pipeline_variable, key: 'B', value: '$C'),
+ build(:ci_pipeline_variable, key: 'C', value: '$A')
+ ]
+ end
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ 'rspec: circular variable reference detected: ["A", "B", "C"]')
end
- it_behaves_like 'build seed'
+ context 'with job:rules:[if:]' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$C != null', when: 'always' }] } }
+
+ it "included? does not raise" do
+ expect { subject.included? }.not_to raise_error
+ end
- it 'initializes the build twice' do
- expect(Ci::Build).to receive(:new).twice.and_call_original
- seed_build.to_resource
+ it "included? returns true" do
+ expect(subject.included?).to eq(true)
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
index 68e70525c55..93644aa1497 100644
--- a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
@@ -4,19 +4,25 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
let(:codequality_report) { described_class.new }
- let(:degradation_1) { build(:codequality_degradation_1) }
- let(:degradation_2) { build(:codequality_degradation_2) }
+ let(:degradation_major) { build(:codequality_degradation, :major) }
+ let(:degradation_minor) { build(:codequality_degradation, :minor) }
+ let(:degradation_blocker) { build(:codequality_degradation, :blocker) }
+ let(:degradation_info) { build(:codequality_degradation, :info) }
+ let(:degradation_major_2) { build(:codequality_degradation, :major) }
+ let(:degradation_critical) { build(:codequality_degradation, :critical) }
+ let(:degradation_uppercase_major) { build(:codequality_degradation, severity: 'MAJOR') }
+ let(:degradation_unknown) { build(:codequality_degradation, severity: 'unknown') }
it { expect(codequality_report.degradations).to eq({}) }
describe '#add_degradation' do
context 'when there is a degradation' do
before do
- codequality_report.add_degradation(degradation_1)
+ codequality_report.add_degradation(degradation_major)
end
it 'adds degradation to codequality report' do
- expect(codequality_report.degradations.keys).to eq([degradation_1[:fingerprint]])
+ expect(codequality_report.degradations.keys).to match_array([degradation_major[:fingerprint]])
expect(codequality_report.degradations.values.size).to eq(1)
end
end
@@ -53,8 +59,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
context 'when there are many degradations' do
before do
- codequality_report.add_degradation(degradation_1)
- codequality_report.add_degradation(degradation_2)
+ codequality_report.add_degradation(degradation_major)
+ codequality_report.add_degradation(degradation_minor)
end
it 'returns the number of degradations' do
@@ -68,36 +74,25 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
context 'when there are many degradations' do
before do
- codequality_report.add_degradation(degradation_1)
- codequality_report.add_degradation(degradation_2)
+ codequality_report.add_degradation(degradation_major)
+ codequality_report.add_degradation(degradation_minor)
end
it 'returns all degradations' do
- expect(all_degradations).to contain_exactly(degradation_1, degradation_2)
+ expect(all_degradations).to contain_exactly(degradation_major, degradation_minor)
end
end
end
describe '#sort_degradations!' do
- let(:major) { build(:codequality_degradation, :major) }
- let(:minor) { build(:codequality_degradation, :minor) }
- let(:blocker) { build(:codequality_degradation, :blocker) }
- let(:info) { build(:codequality_degradation, :info) }
- let(:major_2) { build(:codequality_degradation, :major) }
- let(:critical) { build(:codequality_degradation, :critical) }
- let(:uppercase_major) { build(:codequality_degradation, severity: 'MAJOR') }
- let(:unknown) { build(:codequality_degradation, severity: 'unknown') }
-
- let(:codequality_report) { described_class.new }
-
before do
- codequality_report.add_degradation(major)
- codequality_report.add_degradation(minor)
- codequality_report.add_degradation(blocker)
- codequality_report.add_degradation(major_2)
- codequality_report.add_degradation(info)
- codequality_report.add_degradation(critical)
- codequality_report.add_degradation(unknown)
+ codequality_report.add_degradation(degradation_major)
+ codequality_report.add_degradation(degradation_minor)
+ codequality_report.add_degradation(degradation_blocker)
+ codequality_report.add_degradation(degradation_major_2)
+ codequality_report.add_degradation(degradation_info)
+ codequality_report.add_degradation(degradation_critical)
+ codequality_report.add_degradation(degradation_unknown)
codequality_report.sort_degradations!
end
@@ -105,36 +100,70 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
it 'sorts degradations based on severity' do
expect(codequality_report.degradations.values).to eq(
[
- blocker,
- critical,
- major,
- major_2,
- minor,
- info,
- unknown
+ degradation_blocker,
+ degradation_critical,
+ degradation_major,
+ degradation_major_2,
+ degradation_minor,
+ degradation_info,
+ degradation_unknown
])
end
context 'with non-existence and uppercase severities' do
let(:other_report) { described_class.new }
- let(:non_existent) { build(:codequality_degradation, severity: 'non-existent') }
+ let(:degradation_non_existent) { build(:codequality_degradation, severity: 'non-existent') }
before do
- other_report.add_degradation(blocker)
- other_report.add_degradation(uppercase_major)
- other_report.add_degradation(minor)
- other_report.add_degradation(non_existent)
+ other_report.add_degradation(degradation_blocker)
+ other_report.add_degradation(degradation_uppercase_major)
+ other_report.add_degradation(degradation_minor)
+ other_report.add_degradation(degradation_non_existent)
end
it 'sorts unknown last' do
expect(other_report.degradations.values).to eq(
[
- blocker,
- uppercase_major,
- minor,
- non_existent
+ degradation_blocker,
+ degradation_uppercase_major,
+ degradation_minor,
+ degradation_non_existent
])
end
end
end
+
+ describe '#code_quality_report_summary' do
+ context "when there is no degradation" do
+ it 'return nil' do
+ expect(codequality_report.code_quality_report_summary).to eq(nil)
+ end
+ end
+
+ context "when there are degradations" do
+ before do
+ codequality_report.add_degradation(degradation_major)
+ codequality_report.add_degradation(degradation_major_2)
+ codequality_report.add_degradation(degradation_minor)
+ codequality_report.add_degradation(degradation_blocker)
+ codequality_report.add_degradation(degradation_info)
+ codequality_report.add_degradation(degradation_critical)
+ codequality_report.add_degradation(degradation_unknown)
+ end
+
+ it 'returns the summary of the code quality report' do
+ expect(codequality_report.code_quality_report_summary).to eq(
+ {
+ 'major' => 2,
+ 'minor' => 1,
+ 'blocker' => 1,
+ 'info' => 1,
+ 'critical' => 1,
+ 'unknown' => 1,
+ 'count' => 7
+ }
+ )
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/runner_instructions_spec.rb b/spec/lib/gitlab/ci/runner_instructions_spec.rb
index 56f69720b87..31c53d4a030 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 do
+RSpec.describe Gitlab::Ci::RunnerInstructions, feature_category: :runner_fleet do
using RSpec::Parameterized::TableSyntax
let(:params) { {} }
@@ -29,7 +29,6 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
context name do
it 'has the required fields' do
expect(subject).to have_key(:human_readable_name)
- expect(subject).to have_key(:installation_instructions_url)
end
end
end
diff --git a/spec/lib/gitlab/ci/runner_releases_spec.rb b/spec/lib/gitlab/ci/runner_releases_spec.rb
index ad1e9b12b8a..14f3c95ec79 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 do
+RSpec.describe Gitlab::Ci::RunnerReleases, feature_category: :runner_fleet 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 55c3834bfa7..526d6cba657 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 do
+RSpec.describe Gitlab::Ci::RunnerUpgradeCheck, feature_category: :runner_fleet do
using RSpec::Parameterized::TableSyntax
subject(:instance) { described_class.new(gitlab_version, runner_releases) }
@@ -51,8 +51,8 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
context 'with runner_version from last minor release' do
let(:runner_version) { 'v14.0.1' }
- it 'returns :not_available' do
- is_expected.to eq([parsed_runner_version, :not_available])
+ it 'returns :unavailable' do
+ is_expected.to eq([parsed_runner_version, :unavailable])
end
end
end
@@ -85,8 +85,8 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
context 'with a runner_version that is too recent' do
let(:runner_version) { 'v14.2.0' }
- it 'returns :not_available' do
- is_expected.to eq([parsed_runner_version, :not_available])
+ it 'returns :unavailable' do
+ is_expected.to eq([parsed_runner_version, :unavailable])
end
end
end
@@ -96,14 +96,14 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
context 'with valid params' do
where(:runner_version, :expected_status, :expected_suggested_version) do
- 'v15.0.0' | :not_available | '15.0.0' # not available since the GitLab instance is still on 14.x, a major version might be incompatible, and a patch upgrade is not available
+ 'v15.0.0' | :unavailable | '15.0.0' # not available since the GitLab instance is still on 14.x, a major version might be incompatible, and a patch upgrade is not available
'v14.1.0-rc3' | :recommended | '14.1.1' # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
'v14.1.0~beta.1574.gf6ea9389' | :recommended | '14.1.1' # suffixes are correctly handled
'v14.1.0/1.1.0' | :recommended | '14.1.1' # suffixes are correctly handled
'v14.1.0' | :recommended | '14.1.1' # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
'v14.0.1' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available
'v14.0.2-rc1' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available and we'll move out of a release candidate
- 'v14.0.2' | :not_available | '14.0.2' # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
+ 'v14.0.2' | :unavailable | '14.0.2' # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
'v13.10.1' | :available | '14.0.2' # available upgrade: 14.0.2
'v13.10.1~beta.1574.gf6ea9389' | :recommended | '13.10.1' # suffixes are correctly handled, official 13.10.1 is available
'v13.10.1/1.1.0' | :recommended | '13.10.1' # suffixes are correctly handled, official 13.10.1 is available
@@ -125,13 +125,13 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
context 'with valid params' do
where(:runner_version, :expected_status, :expected_suggested_version) do
- 'v14.0.0' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available, even though the GitLab instance is still on 13.x and a major version might be incompatible
- 'v13.10.1' | :not_available | '13.10.1' # not available since 13.10.1 is already ahead of GitLab instance version and is the latest patch update for 13.10.x
- 'v13.10.0' | :recommended | '13.10.1' # recommended upgrade since 13.10.1 is available
- 'v13.9.2' | :not_available | '13.9.2' # not_available even though backports are no longer released for this version because the runner is already on the same version as the GitLab version
- 'v13.9.0' | :recommended | '13.9.2' # recommended upgrade since backports are no longer released for this version
- 'v13.8.1' | :recommended | '13.9.2' # recommended upgrade since build is too old (missing in records)
- 'v11.4.1' | :recommended | '13.9.2' # recommended upgrade since build is too old (missing in records)
+ 'v14.0.0' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available, even though the GitLab instance is still on 13.x and a major version might be incompatible
+ 'v13.10.1' | :unavailable | '13.10.1' # not available since 13.10.1 is already ahead of GitLab instance version and is the latest patch update for 13.10.x
+ 'v13.10.0' | :recommended | '13.10.1' # recommended upgrade since 13.10.1 is available
+ 'v13.9.2' | :unavailable | '13.9.2' # not available even though backports are no longer released for this version because the runner is already on the same version as the GitLab version
+ 'v13.9.0' | :recommended | '13.9.2' # recommended upgrade since backports are no longer released for this version
+ 'v13.8.1' | :recommended | '13.9.2' # recommended upgrade since build is too old (missing in records)
+ 'v11.4.1' | :recommended | '13.9.2' # recommended upgrade since build is too old (missing in records)
end
with_them do
diff --git a/spec/lib/gitlab/ci/status/bridge/common_spec.rb b/spec/lib/gitlab/ci/status/bridge/common_spec.rb
index 37524afc83d..fef97c73a91 100644
--- a/spec/lib/gitlab/ci/status/bridge/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/bridge/common_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Status::Bridge::Common do
+RSpec.describe Gitlab::Ci::Status::Bridge::Common, feature_category: :continuous_integration do
let_it_be(:user) { create(:user) }
let_it_be(:bridge) { create(:ci_bridge) }
let_it_be(:downstream_pipeline) { create(:ci_pipeline) }
@@ -37,4 +37,35 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Common do
it { expect(subject.details_path).to be_nil }
end
end
+
+ describe '#label' do
+ let(:description) { 'my description' }
+ let(:bridge) { create(:ci_bridge, description: description) }
+
+ subject do
+ Gitlab::Ci::Status::Created
+ .new(bridge, user)
+ .extend(described_class)
+ end
+
+ it 'returns description' do
+ expect(subject.label).to eq description
+ end
+
+ context 'when description is nil' do
+ let(:description) { nil }
+
+ it 'returns core status label' do
+ expect(subject.label).to eq('created')
+ end
+ end
+
+ context 'when description is empty string' do
+ let(:description) { '' }
+
+ it 'returns core status label' do
+ expect(subject.label).to eq('created')
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
index c13901a4776..040c3ec7f6e 100644
--- a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
expect(status.text).to eq s_('CiStatusText|created')
expect(status.icon).to eq 'status_created'
expect(status.favicon).to eq 'favicon_status_created'
- expect(status.label).to be_nil
+ expect(status.label).to eq 'created'
expect(status).not_to have_details
expect(status).not_to have_action
end
@@ -40,7 +40,8 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Bridge::Failed]
+ .to eq [Gitlab::Ci::Status::Bridge::Retryable,
+ Gitlab::Ci::Status::Bridge::Failed]
end
it 'fabricates a failed bridge status' do
@@ -51,10 +52,10 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
expect(status.text).to eq s_('CiStatusText|failed')
expect(status.icon).to eq 'status_failed'
expect(status.favicon).to eq 'favicon_status_failed'
- expect(status.label).to be_nil
+ expect(status.label).to eq 'failed'
expect(status.status_tooltip).to eq "#{s_('CiStatusText|failed')} - (unknown failure)"
expect(status).not_to have_details
- expect(status).not_to have_action
+ expect(status).to have_action
end
context 'failed with downstream_pipeline_creation_failed' do
@@ -130,12 +131,36 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory, feature_category: :continuou
expect(status.text).to eq 'waiting'
expect(status.group).to eq 'waiting-for-resource'
expect(status.icon).to eq 'status_pending'
- expect(status.favicon).to eq 'favicon_pending'
+ expect(status.favicon).to eq 'favicon_status_pending'
expect(status.illustration).to include(:image, :size, :title)
expect(status).not_to have_details
end
end
+ context 'when the bridge is successful and therefore retryable' do
+ let(:bridge) { create(:ci_bridge, :success) }
+
+ it 'matches correct core status' do
+ expect(factory.core_status).to be_a Gitlab::Ci::Status::Success
+ end
+
+ it 'matches correct extended statuses' do
+ expect(factory.extended_statuses)
+ .to eq [Gitlab::Ci::Status::Bridge::Retryable]
+ end
+
+ it 'fabricates a retryable build status' do
+ expect(status).to be_a Gitlab::Ci::Status::Bridge::Retryable
+ end
+
+ it 'fabricates status with correct details' do
+ expect(status.text).to eq s_('CiStatusText|passed')
+ expect(status.icon).to eq 'status_success'
+ expect(status.favicon).to eq 'favicon_status_success'
+ expect(status).to have_action
+ end
+ end
+
private
def create_bridge(*traits)
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index ade07a54877..2c93f842a30 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -75,7 +75,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Play do
end
describe '#action_button_title' do
- it { expect(subject.action_button_title).to eq 'Trigger this manual action' }
+ it { expect(subject.action_button_title).to eq 'Run job' }
end
describe '.matches?' do
diff --git a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
index bb6139accaf..6f5ab77a358 100644
--- a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
+++ b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Gitlab::Ci::Status::WaitingForResource do
end
describe '#favicon' do
- it { expect(subject.favicon).to eq 'favicon_pending' }
+ it { expect(subject.favicon).to eq 'favicon_status_pending' }
end
describe '#group' do
diff --git a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb
index 43deb465025..e4cee379f40 100644
--- a/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/5_minute_production_app_ci_yaml_spec.rb
@@ -2,16 +2,16 @@
require 'spec_helper'
-RSpec.describe '5-Minute-Production-App.gitlab-ci.yml' do
+RSpec.describe '5-Minute-Production-App.gitlab-ci.yml', feature_category: :five_minute_production_app do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('5-Minute-Production-App') }
describe 'the created pipeline' do
- let_it_be(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
+ let_it_be_with_refind(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
let(:user) { project.first_owner }
let(:default_branch) { 'master' }
let(:pipeline_branch) { default_branch }
- let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch) }
let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
@@ -24,24 +24,27 @@ RSpec.describe '5-Minute-Production-App.gitlab-ci.yml' do
end
context 'when AWS variables are set' do
+ def create_ci_variable(key, value)
+ create(:ci_variable, project: project, key: key, value: value)
+ end
+
before do
- create(:ci_variable, project: project, key: 'AWS_ACCESS_KEY_ID', value: 'AKIAIOSFODNN7EXAMPLE')
- create(:ci_variable, project: project, key: 'AWS_SECRET_ACCESS_KEY', value: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY')
- create(:ci_variable, project: project, key: 'AWS_DEFAULT_REGION', value: 'us-west-2')
+ create_ci_variable('AWS_ACCESS_KEY_ID', 'AKIAIOSFODNN7EXAMPLE')
+ create_ci_variable('AWS_SECRET_ACCESS_KEY', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY')
+ create_ci_variable('AWS_DEFAULT_REGION', 'us-west-2')
end
it 'creates all jobs' do
- expect(build_names).to match_array(%w(build terraform_apply deploy terraform_destroy))
+ expect(build_names).to match_array(%w[build terraform_apply deploy terraform_destroy])
end
- context 'pipeline branch is protected' do
+ context 'when pipeline branch is protected' do
before do
create(:protected_branch, project: project, name: pipeline_branch)
- project.reload
end
it 'does not create a destroy job' do
- expect(build_names).to match_array(%w(build terraform_apply deploy))
+ expect(build_names).to match_array(%w[build terraform_apply deploy])
end
end
end
diff --git a/spec/lib/gitlab/ci/templates/Terraform/module_base_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/module_base_gitlab_ci_yaml_spec.rb
new file mode 100644
index 00000000000..9f4f6b02b0b
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/Terraform/module_base_gitlab_ci_yaml_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Terraform/Module-Base.gitlab-ci.yml', feature_category: :continuous_integration do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform/Module-Base') }
+
+ describe 'the created pipeline' do
+ let(:default_branch) { 'main' }
+ let(:pipeline_branch) { default_branch }
+ let_it_be(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
+ let(:user) { project.first_owner }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch) }
+ let(:pipeline) { service.execute(:push).payload }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ allow(project).to receive(:default_branch).and_return(default_branch)
+ end
+
+ it 'does not create any jobs' do
+ expect(build_names).to be_empty
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/terraform_module_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_module_gitlab_ci_yaml_spec.rb
new file mode 100644
index 00000000000..7c3c1776111
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/terraform_module_gitlab_ci_yaml_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Terraform-Module.gitlab-ci.yml', feature_category: :continuous_integration do
+ before do
+ allow(Gitlab::Template::GitlabCiYmlTemplate).to receive(:excluded_patterns).and_return([])
+ end
+
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform-Module') }
+
+ shared_examples 'on any branch' do
+ it 'creates fmt and kics job', :aggregate_failures do
+ expect(pipeline.errors).to be_empty
+ expect(build_names).to include('fmt', 'kics-iac-sast')
+ end
+
+ it 'does not create a deploy job', :aggregate_failures do
+ expect(pipeline.errors).to be_empty
+ expect(build_names).not_to include('deploy')
+ end
+ end
+
+ let_it_be(:project) { create(:project, :repository, create_branch: 'patch-1', create_tag: '1.0.0') }
+ let_it_be(:user) { project.first_owner }
+
+ describe 'the created pipeline' do
+ let(:default_branch) { project.default_branch_or_main }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
+ let(:pipeline) { service.execute(:push).payload }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ allow_next_instance_of(Ci::BuildScheduleWorker) do |instance|
+ allow(instance).to receive(:perform).and_return(true)
+ end
+ allow(project).to receive(:default_branch).and_return(default_branch)
+ end
+
+ context 'when on default branch' do
+ let(:pipeline_ref) { default_branch }
+
+ it_behaves_like 'on any branch'
+ end
+
+ context 'when outside the default branch' do
+ let(:pipeline_ref) { 'patch-1' }
+
+ it_behaves_like 'on any branch'
+ end
+
+ context 'when on tag' do
+ let(:pipeline_ref) { '1.0.0' }
+
+ it 'creates fmt and deploy job', :aggregate_failures do
+ expect(pipeline.errors).to be_empty
+ expect(build_names).to include('fmt', 'deploy')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/trace/archive_spec.rb b/spec/lib/gitlab/ci/trace/archive_spec.rb
index 582c4ad343f..cce6477b91e 100644
--- a/spec/lib/gitlab/ci/trace/archive_spec.rb
+++ b/spec/lib/gitlab/ci/trace/archive_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Trace::Archive do
+RSpec.describe Gitlab::Ci::Trace::Archive, feature_category: :scalability do
context 'with transactional fixtures' do
let_it_be_with_reload(:job) { create(:ci_build, :success, :trace_live) }
let_it_be_with_reload(:trace_metadata) { create(:ci_build_trace_metadata, build: job) }
diff --git a/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
new file mode 100644
index 00000000000..a5365ae53b8
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/builder/pipeline_spec.rb
@@ -0,0 +1,336 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Variables::Builder::Pipeline, feature_category: :pipeline_authoring do
+ let_it_be(:project) { create_default(:project, :repository, create_tag: 'test').freeze }
+ let_it_be(:user) { create(:user) }
+
+ let(:pipeline) { build(:ci_empty_pipeline, :created, project: project) }
+
+ describe '#predefined_variables' do
+ subject { described_class.new(pipeline).predefined_variables }
+
+ it 'includes all predefined variables in a valid order' do
+ keys = subject.pluck(:key)
+
+ expect(keys).to contain_exactly(*%w[
+ CI_PIPELINE_IID
+ CI_PIPELINE_SOURCE
+ CI_PIPELINE_CREATED_AT
+ CI_COMMIT_SHA
+ CI_COMMIT_SHORT_SHA
+ CI_COMMIT_BEFORE_SHA
+ CI_COMMIT_REF_NAME
+ CI_COMMIT_REF_SLUG
+ CI_COMMIT_BRANCH
+ CI_COMMIT_MESSAGE
+ CI_COMMIT_TITLE
+ CI_COMMIT_DESCRIPTION
+ CI_COMMIT_REF_PROTECTED
+ CI_COMMIT_TIMESTAMP
+ CI_COMMIT_AUTHOR
+ CI_BUILD_REF
+ CI_BUILD_BEFORE_SHA
+ CI_BUILD_REF_NAME
+ CI_BUILD_REF_SLUG
+ ])
+ end
+
+ context 'when the pipeline is running for a tag' do
+ let(:pipeline) { build(:ci_empty_pipeline, :created, project: project, ref: 'test', tag: true) }
+
+ it 'includes all predefined variables in a valid order' do
+ keys = subject.pluck(:key)
+
+ expect(keys).to contain_exactly(*%w[
+ CI_PIPELINE_IID
+ CI_PIPELINE_SOURCE
+ CI_PIPELINE_CREATED_AT
+ CI_COMMIT_SHA
+ CI_COMMIT_SHORT_SHA
+ CI_COMMIT_BEFORE_SHA
+ CI_COMMIT_REF_NAME
+ CI_COMMIT_REF_SLUG
+ CI_COMMIT_MESSAGE
+ CI_COMMIT_TITLE
+ CI_COMMIT_DESCRIPTION
+ CI_COMMIT_REF_PROTECTED
+ CI_COMMIT_TIMESTAMP
+ CI_COMMIT_AUTHOR
+ CI_BUILD_REF
+ CI_BUILD_BEFORE_SHA
+ CI_BUILD_REF_NAME
+ CI_BUILD_REF_SLUG
+ CI_COMMIT_TAG
+ CI_COMMIT_TAG_MESSAGE
+ CI_BUILD_TAG
+ ])
+ end
+ end
+
+ context 'when merge request is present' do
+ 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) do
+ create(:merge_request, :simple,
+ source_project: project,
+ target_project: project,
+ assignees: assignees,
+ milestone: milestone,
+ labels: labels)
+ end
+
+ context 'when pipeline for merge request is created' do
+ let(:pipeline) do
+ create(:ci_pipeline, :detached_merge_request_pipeline,
+ ci_ref_presence: false,
+ user: user,
+ merge_request: merge_request)
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'exposes merge request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
+ 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
+ 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
+ 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED' => ProtectedBranch.protected?(
+ merge_request.target_project,
+ merge_request.target_branch
+ ).to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => '',
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => '',
+ 'CI_MERGE_REQUEST_TITLE' => merge_request.title,
+ 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
+ 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
+ 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
+ 'CI_MERGE_REQUEST_EVENT_TYPE' => 'detached',
+ 'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true))
+ end
+
+ it 'exposes diff variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DIFF_ID' => merge_request.merge_request_diff.id.to_s,
+ 'CI_MERGE_REQUEST_DIFF_BASE_SHA' => merge_request.merge_request_diff.base_commit_sha)
+ end
+
+ context 'without assignee' do
+ let(:assignees) { [] }
+
+ it 'does not expose assignee variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES')
+ end
+ end
+
+ context 'without milestone' do
+ let(:milestone) { nil }
+
+ it 'does not expose milestone variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE')
+ end
+ end
+
+ context 'without labels' do
+ let(:labels) { [] }
+
+ it 'does not expose labels variable' do
+ expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS')
+ end
+ end
+ end
+
+ context 'when pipeline on branch is created' do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, user: user, ref: 'feature')
+ end
+
+ context 'when a merge request is created' do
+ before do
+ merge_request
+ end
+
+ context 'when user has access to project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'merge request references are returned matching the pipeline' do
+ expect(subject.to_hash).to include(
+ 'CI_OPEN_MERGE_REQUESTS' => merge_request.to_reference(full: true))
+ end
+ end
+
+ context 'when user does not have access to project' do
+ it 'CI_OPEN_MERGE_REQUESTS is not returned' do
+ expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS')
+ end
+ end
+ end
+
+ context 'when no a merge request is created' do
+ it 'CI_OPEN_MERGE_REQUESTS is not returned' do
+ expect(subject.to_hash).not_to have_key('CI_OPEN_MERGE_REQUESTS')
+ end
+ end
+ end
+
+ context 'with merged results' do
+ let(:pipeline) do
+ create(:ci_pipeline, :merged_result_pipeline, merge_request: merge_request)
+ end
+
+ it 'exposes merge request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
+ 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
+ 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
+ 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED' => ProtectedBranch.protected?(
+ merge_request.target_project,
+ merge_request.target_branch
+ ).to_s,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => merge_request.target_branch_sha,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
+ '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_ASSIGNEES' => merge_request.assignee_username_list,
+ 'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
+ 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).sort.join(','),
+ 'CI_MERGE_REQUEST_EVENT_TYPE' => 'merged_result')
+ end
+
+ it 'exposes diff variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_DIFF_ID' => merge_request.merge_request_diff.id.to_s,
+ 'CI_MERGE_REQUEST_DIFF_BASE_SHA' => merge_request.merge_request_diff.base_commit_sha)
+ end
+ end
+ end
+
+ context 'when source is external pull request' do
+ let(:pipeline) do
+ create(:ci_pipeline, source: :external_pull_request_event, external_pull_request: pull_request)
+ end
+
+ let(:pull_request) { create(:external_pull_request, project: project) }
+
+ it 'exposes external pull request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_EXTERNAL_PULL_REQUEST_IID' => pull_request.pull_request_iid.to_s,
+ 'CI_EXTERNAL_PULL_REQUEST_SOURCE_REPOSITORY' => pull_request.source_repository,
+ 'CI_EXTERNAL_PULL_REQUEST_TARGET_REPOSITORY' => pull_request.target_repository,
+ 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA' => pull_request.source_sha,
+ 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA' => pull_request.target_sha,
+ 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME' => pull_request.source_branch,
+ 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME' => pull_request.target_branch
+ )
+ end
+ end
+
+ describe 'variable CI_KUBERNETES_ACTIVE' do
+ context 'when pipeline.has_kubernetes_active? is true' do
+ before do
+ allow(pipeline).to receive(:has_kubernetes_active?).and_return(true)
+ end
+
+ it "is included with value 'true'" do
+ expect(subject.to_hash).to include('CI_KUBERNETES_ACTIVE' => 'true')
+ end
+ end
+
+ context 'when pipeline.has_kubernetes_active? is false' do
+ before do
+ allow(pipeline).to receive(:has_kubernetes_active?).and_return(false)
+ end
+
+ it 'is not included' do
+ expect(subject.to_hash).not_to have_key('CI_KUBERNETES_ACTIVE')
+ end
+ end
+ end
+
+ describe 'variable CI_GITLAB_FIPS_MODE' do
+ context 'when FIPS flag is enabled' do
+ before do
+ allow(Gitlab::FIPS).to receive(:enabled?).and_return(true)
+ end
+
+ it "is included with value 'true'" do
+ expect(subject.to_hash).to include('CI_GITLAB_FIPS_MODE' => 'true')
+ end
+ end
+
+ context 'when FIPS flag is disabled' do
+ before do
+ allow(Gitlab::FIPS).to receive(:enabled?).and_return(false)
+ end
+
+ it 'is not included' do
+ expect(subject.to_hash).not_to have_key('CI_GITLAB_FIPS_MODE')
+ end
+ end
+ end
+
+ context 'when tag is not found' do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, ref: 'not_found_tag', tag: true)
+ end
+
+ it 'does not expose tag variables' do
+ expect(subject.to_hash.keys)
+ .not_to include(
+ 'CI_COMMIT_TAG',
+ 'CI_COMMIT_TAG_MESSAGE',
+ 'CI_BUILD_TAG'
+ )
+ end
+ end
+
+ context 'without a commit' do
+ let(:pipeline) { build(:ci_empty_pipeline, :created, sha: nil) }
+
+ it 'does not expose commit variables' do
+ expect(subject.to_hash.keys)
+ .not_to include(
+ 'CI_COMMIT_SHA',
+ 'CI_COMMIT_SHORT_SHA',
+ 'CI_COMMIT_BEFORE_SHA',
+ 'CI_COMMIT_REF_NAME',
+ 'CI_COMMIT_REF_SLUG',
+ 'CI_COMMIT_BRANCH',
+ 'CI_COMMIT_TAG',
+ 'CI_COMMIT_MESSAGE',
+ 'CI_COMMIT_TITLE',
+ 'CI_COMMIT_DESCRIPTION',
+ 'CI_COMMIT_REF_PROTECTED',
+ 'CI_COMMIT_TIMESTAMP',
+ 'CI_COMMIT_AUTHOR')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
index 5aa752ee429..bbd3dc54e6a 100644
--- a/spec/lib/gitlab/ci/variables/builder_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -166,8 +166,14 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache, featur
end
before do
+ pipeline_variables_builder = double(
+ ::Gitlab::Ci::Variables::Builder::Pipeline,
+ predefined_variables: [var('C', 3), var('D', 3)]
+ )
+
allow(builder).to receive(:predefined_variables) { [var('A', 1), var('B', 1)] }
allow(pipeline.project).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] }
+ allow(builder).to receive(:pipeline_variables_builder) { pipeline_variables_builder }
allow(pipeline).to receive(:predefined_variables) { [var('C', 3), var('D', 3)] }
allow(job).to receive(:runner) { double(predefined_variables: [var('D', 4), var('E', 4)]) }
allow(builder).to receive(:kubernetes_variables) { [var('E', 5), var('F', 5)] }
@@ -635,8 +641,13 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache, featur
end
before do
+ pipeline_variables_builder = double(
+ ::Gitlab::Ci::Variables::Builder::Pipeline,
+ predefined_variables: [var('B', 2), var('C', 2)]
+ )
+
allow(pipeline.project).to receive(:predefined_variables) { [var('A', 1), var('B', 1)] }
- allow(pipeline).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] }
+ allow(builder).to receive(:pipeline_variables_builder) { pipeline_variables_builder }
allow(builder).to receive(:secret_instance_variables) { [var('C', 3), var('D', 3)] }
allow(builder).to receive(:secret_group_variables) { [var('D', 4), var('E', 4)] }
allow(builder).to receive(:secret_project_variables) { [var('E', 5), var('F', 5)] }
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index b9f65ff749d..360686ce65c 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1503,7 +1503,7 @@ module Gitlab
end
context "when the included internal file is not present" do
- it_behaves_like 'returns errors', "Local file `/local.gitlab-ci.yml` does not exist!"
+ it_behaves_like 'returns errors', "Local file `local.gitlab-ci.yml` does not exist!"
end
end
end