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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 14:59:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 14:59:07 +0300
commit8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca (patch)
tree544930fb309b30317ae9797a9683768705d664c4 /spec/lib/gitlab/ci
parent4b1de649d0168371549608993deac953eb692019 (diff)
Add latest changes from gitlab-org/gitlab@13-7-stable-eev13.7.0-rc42
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r--spec/lib/gitlab/ci/ansi2json/result_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2json/style_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/config/entry/allow_failure_spec.rb92
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/config/entry/need_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/config/entry/needs_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/services_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/mask_secret_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb138
-rw-r--r--spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb749
-rw-r--r--spec/lib/gitlab/ci/parsers_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb109
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb95
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb66
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb75
-rw-r--r--spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb153
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb308
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_spec.rb136
-rw-r--r--spec/lib/gitlab/ci/reports/reports_comparer_spec.rb97
-rw-r--r--spec/lib/gitlab/ci/templates/npm_spec.rb92
-rw-r--r--spec/lib/gitlab/ci/trace/checksum_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb106
34 files changed, 2376 insertions, 284 deletions
diff --git a/spec/lib/gitlab/ci/ansi2json/result_spec.rb b/spec/lib/gitlab/ci/ansi2json/result_spec.rb
index 31c0da95f0a..b7b4d6de8b9 100644
--- a/spec/lib/gitlab/ci/ansi2json/result_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/result_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Gitlab::Ci::Ansi2json::Result do
{ lines: [], state: state, append: false, truncated: false, offset: offset, stream: stream }
end
- subject { described_class.new(params) }
+ subject { described_class.new(**params) }
describe '#size' do
before do
diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
index d27a642ecf3..ff70ff69aaa 100644
--- a/spec/lib/gitlab/ci/ansi2json/style_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Ansi2json::Style do
describe '#set?' do
- subject { described_class.new(params).set? }
+ subject { described_class.new(**params).set? }
context 'when fg color is set' do
let(:params) { { fg: 'term-fg-black' } }
@@ -44,7 +44,7 @@ RSpec.describe Gitlab::Ci::Ansi2json::Style do
end
describe 'update formats to mimic terminals' do
- subject { described_class.new(params) }
+ subject { described_class.new(**params) }
context 'when fg color present' do
let(:params) { { fg: 'term-fg-black', mask: mask } }
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index 77b8aa1d591..efe99cd276c 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -142,7 +142,7 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata do
it 'reads expected number of entries' do
stream = File.open(tmpfile.path)
- metadata = described_class.new(stream, 'public', { recursive: true })
+ metadata = described_class.new(stream, 'public', recursive: true)
expect(metadata.find_entries!.count).to eq entry_count
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause_spec.rb
new file mode 100644
index 00000000000..faede7a361f
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause do
+ describe '.fabricate' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:value) { 'some value' }
+
+ subject { described_class.fabricate(type, value) }
+
+ context 'when type is valid' do
+ where(:type, :result) do
+ 'changes' | Gitlab::Ci::Build::Rules::Rule::Clause::Changes
+ 'exists' | Gitlab::Ci::Build::Rules::Rule::Clause::Exists
+ 'if' | Gitlab::Ci::Build::Rules::Rule::Clause::If
+ end
+
+ with_them do
+ it { is_expected.to be_instance_of(result) }
+ end
+ end
+
+ context 'when type is invalid' do
+ let(:type) { 'when' }
+
+ it { is_expected.to be_nil }
+
+ context "when type is 'variables'" do
+ let(:type) { 'variables' }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
index cbeae33fbcf..a1af5b75f87 100644
--- a/spec/lib/gitlab/ci/build/rules_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -104,7 +104,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
context 'with one rule without any clauses' do
let(:rule_list) { [{ when: 'manual', allow_failure: true }] }
- it { is_expected.to eq(described_class::Result.new('manual', nil, true)) }
+ it { is_expected.to eq(described_class::Result.new('manual', nil, true, nil)) }
end
context 'with one matching rule' do
@@ -171,7 +171,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
context 'with matching rule' do
let(:rule_list) { [{ if: '$VAR == null', allow_failure: true }] }
- it { is_expected.to eq(described_class::Result.new('on_success', nil, true)) }
+ it { is_expected.to eq(described_class::Result.new('on_success', nil, true, nil)) }
end
context 'with non-matching rule' do
@@ -180,18 +180,60 @@ RSpec.describe Gitlab::Ci::Build::Rules do
it { is_expected.to eq(described_class::Result.new('never')) }
end
end
+
+ context 'with variables' do
+ context 'with matching rule' do
+ let(:rule_list) { [{ if: '$VAR == null', variables: { MY_VAR: 'my var' } }] }
+
+ it { is_expected.to eq(described_class::Result.new('on_success', nil, nil, { MY_VAR: 'my var' })) }
+ end
+ end
end
describe 'Gitlab::Ci::Build::Rules::Result' do
let(:when_value) { 'on_success' }
let(:start_in) { nil }
let(:allow_failure) { nil }
+ let(:variables) { nil }
- subject { Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure) }
+ subject(:result) do
+ Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure, variables)
+ end
describe '#build_attributes' do
+ let(:seed_attributes) { {} }
+
+ subject(:build_attributes) do
+ result.build_attributes(seed_attributes)
+ end
+
it 'compacts nil values' do
- expect(subject.build_attributes).to eq(options: {}, when: 'on_success')
+ is_expected.to eq(options: {}, when: 'on_success')
+ end
+
+ context 'when there are variables in rules' do
+ let(:variables) { { VAR1: 'new var 1', VAR3: 'var 3' } }
+
+ context 'when there are seed variables' do
+ let(:seed_attributes) do
+ { yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true }] }
+ end
+
+ it 'returns yaml_variables with override' do
+ is_expected.to include(
+ yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }]
+ )
+ end
+ end
+
+ context 'when there is not seed variables' do
+ it 'does not return yaml_variables' do
+ is_expected.not_to have_key(:yaml_variables)
+ end
+ end
end
end
@@ -200,7 +242,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
let!(:when_value) { 'never' }
it 'returns false' do
- expect(subject.pass?).to eq(false)
+ expect(result.pass?).to eq(false)
end
end
@@ -208,7 +250,7 @@ RSpec.describe Gitlab::Ci::Build::Rules do
let!(:when_value) { 'on_success' }
it 'returns true' do
- expect(subject.pass?).to eq(true)
+ expect(result.pass?).to eq(true)
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/allow_failure_spec.rb b/spec/lib/gitlab/ci/config/entry/allow_failure_spec.rb
new file mode 100644
index 00000000000..7aaad57f5cd
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/allow_failure_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Entry::AllowFailure do
+ let(:entry) { described_class.new(config.deep_dup) }
+ let(:expected_config) { config }
+
+ describe 'validations' do
+ context 'when entry config value is valid' do
+ shared_examples 'valid entry' do
+ describe '#value' do
+ it 'returns key value' do
+ expect(entry.value).to eq(expected_config)
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'with boolean values' do
+ it_behaves_like 'valid entry' do
+ let(:config) { true }
+ end
+
+ it_behaves_like 'valid entry' do
+ let(:config) { false }
+ end
+ end
+
+ context 'with hash values' do
+ it_behaves_like 'valid entry' do
+ let(:config) { { exit_codes: 137 } }
+ let(:expected_config) { { exit_codes: [137] } }
+ end
+
+ it_behaves_like 'valid entry' do
+ let(:config) { { exit_codes: [42, 137] } }
+ end
+ end
+ end
+
+ context 'when entry value is not valid' do
+ shared_examples 'invalid entry' do
+ describe '#valid?' do
+ it { expect(entry).not_to be_valid }
+ it { expect(entry.errors).to include(error_message) }
+ end
+ end
+
+ context 'when it has a wrong type' do
+ let(:config) { [1] }
+ let(:error_message) do
+ 'allow failure config should be a hash or a boolean value'
+ end
+
+ it_behaves_like 'invalid entry'
+ end
+
+ context 'with string exit codes' do
+ let(:config) { { exit_codes: 'string' } }
+ let(:error_message) do
+ 'allow failure exit codes should be an array of integers or an integer'
+ end
+
+ it_behaves_like 'invalid entry'
+ end
+
+ context 'with array of strings as exit codes' do
+ let(:config) { { exit_codes: ['string 1', 'string 2'] } }
+ let(:error_message) do
+ 'allow failure exit codes should be an array of integers or an integer'
+ end
+
+ it_behaves_like 'invalid entry'
+ end
+
+ context 'when it has an extra keys' do
+ let(:config) { { extra: true } }
+ let(:error_message) do
+ 'allow failure config contains unknown keys: extra'
+ end
+
+ it_behaves_like 'invalid entry'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index 8b2e0410474..b3b7901074a 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -227,6 +227,23 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
end
end
end
+
+ context 'when bridge config contains exit_codes' do
+ let(:config) do
+ { script: 'rspec', allow_failure: { exit_codes: [42] } }
+ end
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns an error message' do
+ expect(subject.errors)
+ .to include(/allow failure should be a boolean value/)
+ end
+ end
+ end
end
describe '#manual_action?' do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index c3d91057328..e810d65d560 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -81,7 +81,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) { { name: 'ruby:2.7', entrypoint: %w(/bin/sh run), ports: ports } }
- let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:entry) { described_class.new(config, with_image_ports: image_ports) }
let(:image_ports) { false }
context 'when with_image_ports metadata is not enabled' do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index e0e8bc93770..7834a1a94f2 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -670,6 +670,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
end
describe '#ignored?' do
+ before do
+ entry.compose!
+ end
+
context 'when job is a manual action' do
context 'when it is not specified if job is allowed to fail' do
let(:config) do
@@ -700,6 +704,16 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
expect(entry).not_to be_ignored
end
end
+
+ context 'when job is dynamically allowed to fail' do
+ let(:config) do
+ { script: 'deploy', when: 'manual', allow_failure: { exit_codes: 42 } }
+ end
+
+ it 'is not an ignored job' do
+ expect(entry).not_to be_ignored
+ end
+ end
end
context 'when job is not a manual action' do
@@ -709,6 +723,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
+
+ it 'does not return allow_failure' do
+ expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
+ end
end
context 'when job is allowed to fail' do
@@ -717,6 +735,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'is an ignored job' do
expect(entry).to be_ignored
end
+
+ it 'does not return allow_failure_criteria' do
+ expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
+ end
end
context 'when job is not allowed to fail' do
@@ -725,6 +747,32 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
it 'is not an ignored job' do
expect(entry).not_to be_ignored
end
+
+ it 'does not return allow_failure_criteria' do
+ expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
+ end
+ end
+
+ context 'when job is dynamically allowed to fail' do
+ let(:config) { { script: 'deploy', allow_failure: { exit_codes: 42 } } }
+
+ it 'is not an ignored job' do
+ expect(entry).not_to be_ignored
+ end
+
+ it 'returns allow_failure_criteria' do
+ expect(entry.value[:allow_failure_criteria]).to match(exit_codes: [42])
+ end
+
+ context 'with ci_allow_failure_with_exit_codes disabled' do
+ before do
+ stub_feature_flags(ci_allow_failure_with_exit_codes: false)
+ end
+
+ it 'does not return allow_failure_criteria' do
+ expect(entry.value.key?(:allow_failure_criteria)).to be_falsey
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb
index 5a826bf8282..983e95fae42 100644
--- a/spec/lib/gitlab/ci/config/entry/need_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb
@@ -165,6 +165,45 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do
end
end
+ context 'with cross pipeline artifacts needs' do
+ context 'when pipeline is provided' do
+ context 'when job is provided' do
+ let(:config) { { job: 'job_name', pipeline: '$THE_PIPELINE_ID' } }
+
+ it { is_expected.to be_valid }
+
+ it 'sets artifacts:true by default' do
+ expect(need.value).to eq(job: 'job_name', pipeline: '$THE_PIPELINE_ID', artifacts: true)
+ end
+
+ it 'sets the type as cross_dependency' do
+ expect(need.type).to eq(:cross_dependency)
+ end
+ end
+
+ context 'when artifacts is provided' do
+ let(:config) { { job: 'job_name', pipeline: '$THE_PIPELINE_ID', artifacts: false } }
+
+ it { is_expected.to be_valid }
+
+ it 'returns the correct value' do
+ expect(need.value).to eq(job: 'job_name', pipeline: '$THE_PIPELINE_ID', artifacts: false)
+ end
+ end
+ end
+
+ context 'when config contains not allowed keys' do
+ let(:config) { { job: 'job_name', pipeline: '$THE_PIPELINE_ID', something: 'else' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error' do
+ expect(need.errors)
+ .to contain_exactly('cross pipeline dependency config contains unknown keys: something')
+ end
+ end
+ end
+
context 'when need config is not a string or a hash' do
let(:config) { :job_name }
diff --git a/spec/lib/gitlab/ci/config/entry/needs_spec.rb b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
index f3b9d0c3c84..f11f2a56f5f 100644
--- a/spec/lib/gitlab/ci/config/entry/needs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/needs_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
subject(:needs) { described_class.new(config) }
before do
- needs.metadata[:allowed_needs] = %i[job]
+ needs.metadata[:allowed_needs] = %i[job cross_dependency]
end
describe 'validations' do
@@ -66,6 +66,27 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Needs do
end
end
end
+
+ context 'with too many cross pipeline dependencies' do
+ let(:limit) { described_class::NEEDS_CROSS_PIPELINE_DEPENDENCIES_LIMIT }
+
+ let(:config) do
+ Array.new(limit.next) do |index|
+ { pipeline: "$UPSTREAM_PIPELINE_#{index}", job: 'job-1' }
+ end
+ end
+
+ describe '#valid?' do
+ it { is_expected.not_to be_valid }
+ end
+
+ describe '#errors' do
+ it 'returns error about incorrect type' do
+ expect(needs.errors).to contain_exactly(
+ "needs config must be less than or equal to #{limit}")
+ end
+ end
+ end
end
describe '.compose!' do
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index ac8dd2a3267..aadf94365c6 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -361,7 +361,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
context 'when root yaml variables are used' do
let(:variables) do
Gitlab::Ci::Config::Entry::Variables.new(
- A: 'root', C: 'root', D: 'root'
+ { A: 'root', C: 'root', D: 'root' }
).value
end
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 79716df6b60..54c7a5c3602 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
image: 'ruby:2.7',
default: {},
services: ['postgres:9.1', 'mysql:5.5'],
- variables: { VAR: 'root' },
+ variables: { VAR: 'root', VAR2: { value: 'val 2', description: 'this is var 2' } },
after_script: ['make clean'],
stages: %w(build pages release),
cache: { key: 'k', untracked: true, paths: ['public/'] },
@@ -80,6 +80,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
.to eq 'List of external YAML files to include.'
end
+ it 'sets correct variables value' do
+ expect(root.variables_value).to eq('VAR' => 'root', 'VAR2' => 'val 2')
+ end
+
describe '#leaf?' do
it 'is not leaf' do
expect(root).not_to be_leaf
@@ -128,7 +132,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
- variables: { 'VAR' => 'root' },
+ variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -142,7 +146,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
- variables: { 'VAR' => 'root' },
+ variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -158,7 +162,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' },
only: { refs: %w(branches tags) },
- variables: { 'VAR' => 'job' },
+ variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
after_script: [],
ignore: false,
scheduling_type: :stage }
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
index 4a43e6c9a86..d1bd22e5573 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -339,6 +339,22 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
end
end
end
+
+ context 'with an invalid variables' do
+ let(:config) do
+ { if: '$THIS == "that"', variables: 'hello' }
+ end
+
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about invalid variables:' do
+ expect(subject.errors).to include(/variables config should be a hash of key value pairs/)
+ end
+ end
end
context 'allow_failure: validation' do
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index ec137ef2ae4..2795cc9dddf 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -96,7 +96,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
{ name: 'postgresql:9.5', alias: 'db', command: %w(cmd run), entrypoint: %w(/bin/sh run), ports: ports }
end
- let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:entry) { described_class.new(config, with_image_ports: image_ports) }
let(:image_ports) { false }
context 'when with_image_ports metadata is not enabled' do
diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb
index e4f8a348d21..85e7f297b03 100644
--- a/spec/lib/gitlab/ci/config/entry/services_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Services do
context 'when configuration has ports' do
let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] }
let(:config) { ['postgresql:9.5', { name: 'postgresql:9.1', alias: 'postgres_old', ports: ports }] }
- let(:entry) { described_class.new(config, { with_image_ports: image_ports }) }
+ let(:entry) { described_class.new(config, with_image_ports: image_ports) }
let(:image_ports) { false }
context 'when with_image_ports metadata is not enabled' do
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index ac33f858f43..426a38e2ef7 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -3,7 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Variables do
- subject { described_class.new(config) }
+ let(:metadata) { {} }
+
+ subject { described_class.new(config, metadata) }
shared_examples 'valid config' do
describe '#value' do
@@ -71,7 +73,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
{ 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' }
end
- it_behaves_like 'valid config'
+ it_behaves_like 'invalid config'
+
+ context 'when metadata has use_value_data' do
+ let(:metadata) { { use_value_data: true } }
+
+ it_behaves_like 'valid config'
+ end
end
context 'when entry value is an array' do
@@ -80,32 +88,36 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variables do
it_behaves_like 'invalid config'
end
- context 'when entry value has hash with other key-pairs' do
- let(:config) do
- { 'VARIABLE_1' => { value: 'value 1', hello: 'variable 1' },
- 'VARIABLE_2' => 'value 2' }
- end
+ context 'when metadata has use_value_data' do
+ let(:metadata) { { use_value_data: true } }
- it_behaves_like 'invalid config'
- end
+ context 'when entry value has hash with other key-pairs' do
+ let(:config) do
+ { 'VARIABLE_1' => { value: 'value 1', hello: 'variable 1' },
+ 'VARIABLE_2' => 'value 2' }
+ end
- context 'when entry config value has hash with nil description' do
- let(:config) do
- { 'VARIABLE_1' => { value: 'value 1', description: nil } }
+ it_behaves_like 'invalid config'
end
- it_behaves_like 'invalid config'
- end
+ context 'when entry config value has hash with nil description' do
+ let(:config) do
+ { 'VARIABLE_1' => { value: 'value 1', description: nil } }
+ end
- context 'when entry config value has hash without description' do
- let(:config) do
- { 'VARIABLE_1' => { value: 'value 1' } }
+ it_behaves_like 'invalid config'
end
- let(:result) do
- { 'VARIABLE_1' => 'value 1' }
- end
+ context 'when entry config value has hash without description' do
+ let(:config) do
+ { 'VARIABLE_1' => { value: 'value 1' } }
+ end
- it_behaves_like 'valid config'
+ let(:result) do
+ { 'VARIABLE_1' => 'value 1' }
+ end
+
+ it_behaves_like 'valid config'
+ end
end
end
diff --git a/spec/lib/gitlab/ci/mask_secret_spec.rb b/spec/lib/gitlab/ci/mask_secret_spec.rb
index 7b2d6b58518..7d950c86700 100644
--- a/spec/lib/gitlab/ci/mask_secret_spec.rb
+++ b/spec/lib/gitlab/ci/mask_secret_spec.rb
@@ -22,6 +22,10 @@ RSpec.describe Gitlab::Ci::MaskSecret do
expect(mask('token', nil)).to eq('token')
end
+ it 'does not change a bytesize of a value' do
+ expect(mask('token-ΓΌ/unicode', 'token-ΓΌ').bytesize).to eq 16
+ end
+
def mask(value, token)
subject.mask!(value.dup, token)
end
diff --git a/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
new file mode 100644
index 00000000000..c6b8cf2a985
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Codequality::CodeClimate do
+ describe '#parse!' do
+ subject(:parse) { described_class.new.parse!(code_climate, codequality_report) }
+
+ let(:codequality_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:code_climate) do
+ [
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }
+ ].to_json
+ end
+
+ context "when data is code_climate style JSON" do
+ context "when there are no degradations" do
+ let(:code_climate) { [].to_json }
+
+ it "returns a codequality report" do
+ expect { parse }.not_to raise_error
+
+ expect(codequality_report.degradations_count).to eq(0)
+ end
+ end
+
+ context "when there are degradations" do
+ it "returns a codequality report" do
+ expect { parse }.not_to raise_error
+
+ expect(codequality_report.degradations_count).to eq(1)
+ end
+ end
+ end
+
+ context "when data is not a valid JSON string" do
+ let(:code_climate) do
+ [
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }
+ ]
+ end
+
+ it "sets error_message" do
+ expect { parse }.not_to raise_error
+
+ expect(codequality_report.error_message).to include('JSON parsing failed')
+ end
+ end
+
+ context 'when degradations contain an invalid one' do
+ let(:code_climate) do
+ [
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "fingerprint": "ab5f8b935886b942d621399aefkaehfiaehf",
+ "severity": "minor"
+ },
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }
+ ].to_json
+ end
+
+ it 'stops parsing the report' do
+ expect { parse }.not_to raise_error
+
+ expect(codequality_report.degradations_count).to eq(0)
+ expect(codequality_report.error_message).to eq("Invalid degradation format: The property '#/' did not contain a required property of 'location'")
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
index 45e87466532..2313378d1e9 100644
--- a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
@@ -4,207 +4,690 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Coverage::Cobertura do
describe '#parse!' do
- subject { described_class.new.parse!(cobertura, coverage_report) }
+ subject(:parse_report) { described_class.new.parse!(cobertura, coverage_report, project_path: project_path, worktree_paths: paths) }
let(:coverage_report) { Gitlab::Ci::Reports::CoverageReports.new }
+ let(:project_path) { 'foo/bar' }
+ let(:paths) { ['app/user.rb'] }
+
+ let(:cobertura) do
+ <<~EOF
+ <coverage>
+ #{sources_xml}
+ #{classes_xml}
+ </coverage>
+ EOF
+ end
context 'when data is Cobertura style XML' do
- context 'when there is no <class>' do
- let(:cobertura) { '' }
+ shared_examples_for 'ignoring sources, project_path, and worktree_paths' do
+ context 'when there is no <class>' do
+ let(:classes_xml) { '' }
- it 'parses XML and returns empty coverage' do
- expect { subject }.not_to raise_error
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
- expect(coverage_report.files).to eq({})
+ expect(coverage_report.files).to eq({})
+ end
end
- end
- context 'when there is a <sources>' do
- shared_examples_for 'ignoring sources' do
- it 'parses XML without errors' do
- expect { subject }.not_to raise_error
+ context 'when there is a single <class>' do
+ context 'with no lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
- expect(coverage_report.files).to eq({})
+ context 'with a single line' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
+ end
+ end
+
+ context 'without a package parent' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages>
+ <class filename="app.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
+ end
+ end
+
+ context 'with multiple lines and methods info' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
+ end
end
end
- context 'and has a single source' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
+ context 'when there are multiple <class>' do
+ context 'without a package parent' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ <class filename="foo.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ </lines></class>
+ </packages>
+ EOF
+ end
+
+ it 'parses XML and returns coverage information per class' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 }, 'foo.rb' => { 6 => 1 } })
+ end
+ end
+
+ context 'with the same filename and different lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with merged coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ end
+ end
+
+ context 'with the same filename and lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="1"/>
+ <line number="2" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with summed-up coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } })
+ end
+ end
+
+ context 'with missing filename' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and ignores class with missing name' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+
+ context 'with invalid line information' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line null="test" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'raises an error' do
+ expect { parse_report }.to raise_error(described_class::InvalidLineInformationError)
+ end
+ end
+ end
+ end
+
+ context 'when there is no <sources>' do
+ let(:sources_xml) { '' }
+
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
+ end
+
+ context 'when there is a <sources>' do
+ context 'and has a single source with a pattern for Go projects' do
+ let(:project_path) { 'local/go' } # Make sure we're not making false positives
+ let(:sources_xml) do
+ <<~EOF
<sources>
- <source>project/src</source>
+ <source>/usr/local/go/src</source>
</sources>
EOF
end
- it_behaves_like 'ignoring sources'
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
end
- context 'and has multiple sources' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
+ context 'and has multiple sources with a pattern for Go projects' do
+ let(:project_path) { 'local/go' } # Make sure we're not making false positives
+ let(:sources_xml) do
+ <<~EOF
<sources>
- <source>project/src/foo</source>
- <source>project/src/bar</source>
+ <source>/usr/local/go/src</source>
+ <source>/go/src</source>
</sources>
EOF
end
- it_behaves_like 'ignoring sources'
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
end
- end
- context 'when there is a single <class>' do
- context 'with no lines' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes><class filename="app.rb"></class></classes>
+ context 'and has a single source but already is at the project root path' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/#{project_path}</source>
+ </sources>
EOF
end
- it 'parses XML and returns empty coverage' do
- expect { subject }.not_to raise_error
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
+ end
- expect(coverage_report.files).to eq({})
+ context 'and has multiple sources but already are at the project root path' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/#{project_path}/</source>
+ <source>builds/somewhere/#{project_path}</source>
+ </sources>
+ EOF
end
+
+ it_behaves_like 'ignoring sources, project_path, and worktree_paths'
end
- context 'with a single line' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><lines>
- <line number="1" hits="2"/>
- </lines></class>
- </classes>
+ context 'and has a single source that is not at the project root path' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/#{project_path}/app</source>
+ </sources>
EOF
end
- it 'parses XML and returns a single file with coverage' do
- expect { subject }.not_to raise_error
+ context 'when there is no <class>' do
+ let(:classes_xml) { '' }
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
- end
- end
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
- context 'with multipe lines and methods info' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- </classes>
- EOF
+ expect(coverage_report.files).to eq({})
+ end
end
- it 'parses XML and returns a single file with coverage' do
- expect { subject }.not_to raise_error
+ context 'when there is a single <class>' do
+ context 'with no lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
+
+ context 'with a single line but the filename cannot be determined based on extracted source and worktree paths' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="member.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
+
+ context 'with a single line' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2 } })
+ end
+ end
+
+ context 'with multiple lines and methods info' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+ end
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
+ context 'when there are multiple <class>' do
+ context 'with the same filename but the filename cannot be determined based on extracted source and worktree paths' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="member.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="member.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
+
+ context 'without a parent package' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="user.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </packages>
+ EOF
+ end
+
+ it 'parses XML and returns coverage information with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ end
+ end
+
+ context 'with the same filename and different lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="user.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with merged coverage, and with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ end
+ end
+
+ context 'with the same filename and lines' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="1"/>
+ <line number="2" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with summed-up coverage, and with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 3, 2 => 1 } })
+ end
+ end
+
+ context 'with missing filename' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and ignores class with missing name' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+
+ context 'with filename that cannot be determined based on extracted source and worktree paths' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="member.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and ignores class with undetermined filename' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+
+ context 'with invalid line information' do
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="user.rb"><methods/><lines>
+ <line null="test" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'raises an error' do
+ expect { parse_report }.to raise_error(described_class::InvalidLineInformationError)
+ end
+ end
end
end
- end
- context 'when there are multipe <class>' do
- context 'with the same filename and different lines' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- <class filename="app.rb"><methods/><lines>
- <line number="6" hits="1"/>
- <line number="7" hits="1"/>
- </lines></class>
- </classes>
+ context 'and has multiple sources that are not at the project root path' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/#{project_path}/app1/</source>
+ <source>builds/#{project_path}/app2/</source>
+ </sources>
EOF
end
- it 'parses XML and returns a single file with merged coverage' do
- expect { subject }.not_to raise_error
-
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ context 'and a class filename is available under multiple extracted sources' do
+ let(:paths) { ['app1/user.rb', 'app2/user.rb'] }
+
+ let(:classes_xml) do
+ <<~EOF
+ <package name="app1">
+ <classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes>
+ </package>
+ <package name="app2">
+ <classes>
+ <class filename="user.rb"><lines>
+ <line number="2" hits="3"/>
+ </lines></class>
+ </classes>
+ </package>
+ EOF
+ end
+
+ it 'parses XML and returns the files with the filename relative to project root' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({
+ 'app1/user.rb' => { 1 => 2 },
+ 'app2/user.rb' => { 2 => 3 }
+ })
+ end
end
- end
- context 'with the same filename and lines' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <packages><package><classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="1"/>
- <line number="2" hits="1"/>
- </lines></class>
- </classes></package></packages>
- EOF
+ context 'and a class filename is available under one of the extracted sources' do
+ let(:paths) { ['app1/member.rb', 'app2/user.rb', 'app2/pet.rb'] }
+
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with the filename relative to project root using the extracted source where it is first found under' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app2/user.rb' => { 1 => 2 } })
+ end
end
- it 'parses XML and returns a single file with summed-up coverage' do
- expect { subject }.not_to raise_error
+ context 'and a class filename is not found under any of the extracted sources' do
+ let(:paths) { ['app1/member.rb', 'app2/pet.rb'] }
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } })
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
end
- end
- context 'with missing filename' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- <class><methods/><lines>
- <line number="6" hits="1"/>
- <line number="7" hits="1"/>
- </lines></class>
- </classes>
- EOF
+ context 'and a class filename is not found under any of the extracted sources within the iteratable limit' do
+ let(:paths) { ['app2/user.rb'] }
+
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="record.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ before do
+ stub_const("#{described_class}::MAX_SOURCES", 1)
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { parse_report }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
end
+ end
+ end
- it 'parses XML and ignores class with missing name' do
- expect { subject }.not_to raise_error
+ shared_examples_for 'non-smart parsing' do
+ let(:sources_xml) do
+ <<~EOF
+ <sources>
+ <source>builds/foo/bar/app</source>
+ </sources>
+ EOF
+ end
- expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
- end
+ let(:classes_xml) do
+ <<~EOF
+ <packages><package name="app"><classes>
+ <class filename="user.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
end
- context 'with invalid line information' do
- let(:cobertura) do
- <<-EOF.strip_heredoc
- <classes>
- <class filename="app.rb"><methods/><lines>
- <line number="1" hits="2"/>
- <line number="2" hits="0"/>
- </lines></class>
- <class filename="app.rb"><methods/><lines>
- <line null="test" hits="1"/>
- <line number="7" hits="1"/>
- </lines></class>
- </classes>
- EOF
- end
+ it 'parses XML and returns filenames unchanged just as how they are found in the class node' do
+ expect { parse_report }.not_to raise_error
- it 'raises an error' do
- expect { subject }.to raise_error(described_class::CoberturaParserError)
- end
+ expect(coverage_report.files).to eq({ 'user.rb' => { 1 => 2 } })
end
end
+
+ context 'when project_path is not present' do
+ let(:project_path) { nil }
+ let(:paths) { ['app/user.rb'] }
+
+ it_behaves_like 'non-smart parsing'
+ end
+
+ context 'when worktree_paths is not present' do
+ let(:project_path) { 'foo/bar' }
+ let(:paths) { nil }
+
+ it_behaves_like 'non-smart parsing'
+ end
end
context 'when data is not Cobertura style XML' do
let(:cobertura) { { coverage: '12%' }.to_json }
it 'raises an error' do
- expect { subject }.to raise_error(described_class::CoberturaParserError)
+ expect { parse_report }.to raise_error(described_class::InvalidXMLError)
end
end
end
diff --git a/spec/lib/gitlab/ci/parsers_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb
index db9a5775d9f..b932cd81272 100644
--- a/spec/lib/gitlab/ci/parsers_spec.rb
+++ b/spec/lib/gitlab/ci/parsers_spec.rb
@@ -30,6 +30,14 @@ RSpec.describe Gitlab::Ci::Parsers do
end
end
+ context 'when file_type is codequality' do
+ let(:file_type) { 'codequality' }
+
+ it 'fabricates the class' do
+ is_expected.to be_a(described_class::Codequality::CodeClimate)
+ end
+ end
+
context 'when file_type is terraform' do
let(:file_type) { 'terraform' }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
new file mode 100644
index 00000000000..78363be7f36
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
+ let_it_be(:namespace) { create(:namespace) }
+ let_it_be(:project, reload: true) { create(:project, namespace: namespace) }
+ let_it_be(:plan_limits, reload: true) { create(:plan_limits, :default_plan) }
+
+ let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2) }
+ let(:save_incompleted) { false }
+
+ let(:command) do
+ double(:command,
+ project: project,
+ pipeline_seed: pipeline_seed,
+ save_incompleted: save_incompleted
+ )
+ end
+
+ let(:pipeline) { build(:ci_pipeline, project: project) }
+ let(:step) { described_class.new(pipeline, command) }
+
+ subject(:perform) { step.perform! }
+
+ context 'when pipeline deployments limit is exceeded' do
+ before do
+ plan_limits.update!(ci_pipeline_deployments: 1)
+ end
+
+ context 'when saving incompleted pipelines' do
+ let(:save_incompleted) { true }
+
+ it 'drops the pipeline' do
+ perform
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.reload).to be_failed
+ end
+
+ it 'breaks the chain' do
+ perform
+
+ expect(step.break?).to be true
+ end
+
+ it 'sets a valid failure reason' do
+ perform
+
+ expect(pipeline.deployments_limit_exceeded?).to be true
+ end
+ end
+
+ context 'when not saving incomplete pipelines' do
+ let(:save_incompleted) { false }
+
+ it 'does not persist the pipeline' do
+ perform
+
+ expect(pipeline).not_to be_persisted
+ end
+
+ it 'breaks the chain' do
+ perform
+
+ expect(step.break?).to be true
+ end
+
+ it 'adds an informative error to the pipeline' do
+ perform
+
+ expect(pipeline.errors.messages).to include(base: ['Pipeline has too many deployments! Requested 2, but the limit is 1.'])
+ end
+ end
+
+ it 'logs the error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ instance_of(Gitlab::Ci::Limit::LimitExceededError),
+ project_id: project.id, plan: namespace.actual_plan_name
+ )
+
+ perform
+ end
+ end
+
+ context 'when pipeline deployments limit is not exceeded' do
+ before do
+ plan_limits.update!(ci_pipeline_deployments: 100)
+ end
+
+ it 'does not break the chain' do
+ perform
+
+ expect(step.break?).to be false
+ end
+
+ it 'does not invalidate the pipeline' do
+ perform
+
+ expect(pipeline.errors).to be_empty
+ end
+
+ it 'does not log any error' do
+ expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
+
+ perform
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index d849c768a3c..0ce8b80902e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -50,8 +50,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
it 'sets the seeds in the command object' do
run_chain
- expect(command.stage_seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
- expect(command.stage_seeds.count).to eq 1
+ expect(command.pipeline_seed).to be_a(Gitlab::Ci::Pipeline::Seed::Pipeline)
+ expect(command.pipeline_seed.size).to eq 1
end
context 'when no ref policy is specified' do
@@ -63,16 +63,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
}
end
- it 'correctly fabricates a stage seeds object' do
+ it 'correctly fabricates stages and builds' do
run_chain
- seeds = command.stage_seeds
- expect(seeds.size).to eq 2
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.second.attributes[:name]).to eq 'deploy'
- expect(seeds.dig(0, 0, :name)).to eq 'rspec'
- expect(seeds.dig(0, 1, :name)).to eq 'spinach'
- expect(seeds.dig(1, 0, :name)).to eq 'production'
+ seed = command.pipeline_seed
+
+ expect(seed.stages.size).to eq 2
+ expect(seed.size).to eq 3
+ expect(seed.stages.first.name).to eq 'test'
+ expect(seed.stages.second.name).to eq 'deploy'
+ expect(seed.stages[0].statuses[0].name).to eq 'rspec'
+ expect(seed.stages[0].statuses[1].name).to eq 'spinach'
+ expect(seed.stages[1].statuses[0].name).to eq 'production'
end
end
@@ -88,14 +90,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
}
end
- it 'returns stage seeds only assigned to master' do
+ it 'returns pipeline seed with jobs only assigned to master' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 1
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ expect(seed.size).to eq 1
+ expect(seed.stages.first.name).to eq 'test'
+ expect(seed.stages[0].statuses[0].name).to eq 'spinach'
end
end
@@ -109,14 +111,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
}
end
- it 'returns stage seeds only assigned to schedules' do
+ it 'returns pipeline seed with jobs only assigned to schedules' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 1
- expect(seeds.first.attributes[:name]).to eq 'test'
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ expect(seed.size).to eq 1
+ expect(seed.stages.first.name).to eq 'test'
+ expect(seed.stages[0].statuses[0].name).to eq 'spinach'
end
end
@@ -141,11 +143,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
it 'returns seeds for kubernetes dependent job' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 2
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
- expect(seeds.dig(1, 0, :name)).to eq 'production'
+ expect(seed.size).to eq 2
+ expect(seed.stages[0].statuses[0].name).to eq 'spinach'
+ expect(seed.stages[1].statuses[0].name).to eq 'production'
end
end
end
@@ -154,10 +156,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
it 'does not return seeds for kubernetes dependent job' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 1
- expect(seeds.dig(0, 0, :name)).to eq 'spinach'
+ expect(seed.size).to eq 1
+ expect(seed.stages[0].statuses[0].name).to eq 'spinach'
end
end
end
@@ -173,10 +175,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
it 'returns stage seeds only when variables expression is truthy' do
run_chain
- seeds = command.stage_seeds
+ seed = command.pipeline_seed
- expect(seeds.size).to eq 1
- expect(seeds.dig(0, 0, :name)).to eq 'unit'
+ expect(seed.size).to eq 1
+ expect(seed.stages[0].statuses[0].name).to eq 'unit'
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
new file mode 100644
index 00000000000..c52994fc6a2
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Quota::Deployments do
+ let_it_be(:namespace) { create(:namespace) }
+ let_it_be(:default_plan, reload: true) { create(:default_plan) }
+ let_it_be(:project, reload: true) { create(:project, :repository, namespace: namespace) }
+ let_it_be(:plan_limits) { create(:plan_limits, plan: default_plan) }
+
+ let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
+
+ let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2)}
+
+ let(:command) do
+ double(:command,
+ project: project,
+ pipeline_seed: pipeline_seed,
+ save_incompleted: true
+ )
+ end
+
+ let(:ci_pipeline_deployments_limit) { 0 }
+
+ before do
+ plan_limits.update!(ci_pipeline_deployments: ci_pipeline_deployments_limit)
+ end
+
+ subject(:quota) { described_class.new(namespace, pipeline, command) }
+
+ shared_context 'limit exceeded' do
+ let(:ci_pipeline_deployments_limit) { 1 }
+ end
+
+ shared_context 'limit not exceeded' do
+ let(:ci_pipeline_deployments_limit) { 2 }
+ end
+
+ describe '#enabled?' do
+ context 'when limit is enabled in plan' do
+ let(:ci_pipeline_deployments_limit) { 10 }
+
+ it 'is enabled' do
+ expect(quota).to be_enabled
+ end
+ end
+
+ context 'when limit is not enabled' do
+ let(:ci_pipeline_deployments_limit) { 0 }
+
+ it 'is not enabled' do
+ expect(quota).not_to be_enabled
+ end
+ end
+
+ context 'when limit does not exist' do
+ before do
+ allow(namespace).to receive(:actual_plan) { create(:default_plan) }
+ end
+
+ it 'is enabled by default' do
+ expect(quota).to be_enabled
+ end
+ end
+ end
+
+ describe '#exceeded?' do
+ context 'when limit is exceeded' do
+ include_context 'limit exceeded'
+
+ it 'is exceeded' do
+ expect(quota).to be_exceeded
+ end
+ end
+
+ context 'when limit is not exceeded' do
+ include_context 'limit not exceeded'
+
+ it 'is not exceeded' do
+ expect(quota).not_to be_exceeded
+ end
+ end
+ end
+
+ describe '#message' do
+ context 'when limit is exceeded' do
+ include_context 'limit exceeded'
+
+ it 'returns info about pipeline deployment limit exceeded' do
+ expect(quota.message)
+ .to eq "Pipeline has too many deployments! Requested 2, but the limit is 1."
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 0b961336f3f..bc10e94c81d 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -71,6 +71,33 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
+ context 'with job:rules:[variables:]' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true }],
+ rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
+ end
+
+ it do
+ is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }])
+ end
+
+ context 'when FF ci_rules_variables is disabled' do
+ before do
+ stub_feature_flags(ci_rules_variables: false)
+ end
+
+ it do
+ is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true }])
+ end
+ end
+ end
+
context 'with cache:key' do
let(:attributes) do
{
@@ -165,6 +192,45 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.to include(options: {}) }
end
+
+ context 'with allow_failure' do
+ let(:options) do
+ { allow_failure_criteria: { exit_codes: [42] } }
+ end
+
+ let(:rules) do
+ [{ if: '$VAR == null', when: 'always' }]
+ 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 }) }
+ 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
+ end
end
describe '#bridge?' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
index e62bf042fba..664aaaedf7b 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
@@ -85,16 +85,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do
end
it_behaves_like 'returning a correct environment'
-
- context 'but the environment auto_stop_in on create flag is disabled' do
- let(:expected_auto_stop_in) { nil }
-
- before do
- stub_feature_flags(environment_auto_stop_start_on_create: false)
- end
-
- it_behaves_like 'returning a correct environment'
- end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
new file mode 100644
index 00000000000..1790388da03
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:stages_attributes) do
+ [
+ {
+ name: 'build',
+ index: 0,
+ builds: [
+ { name: 'init', scheduling_type: :stage },
+ { name: 'build', scheduling_type: :stage }
+ ]
+ },
+ {
+ name: 'test',
+ index: 1,
+ builds: [
+ { name: 'rspec', scheduling_type: :stage },
+ { name: 'staging', scheduling_type: :stage, environment: 'staging' },
+ { name: 'deploy', scheduling_type: :stage, environment: 'production' }
+ ]
+ }
+ ]
+ end
+
+ subject(:seed) do
+ described_class.new(pipeline, stages_attributes)
+ end
+
+ describe '#stages' do
+ it 'returns the stage resources' do
+ stages = seed.stages
+
+ expect(stages).to all(be_a(Ci::Stage))
+ expect(stages.map(&:name)).to contain_exactly('build', 'test')
+ end
+ end
+
+ describe '#size' do
+ it 'returns the number of jobs' do
+ expect(seed.size).to eq(5)
+ end
+ end
+
+ describe '#errors' do
+ context 'when attributes are valid' do
+ it 'returns nil' do
+ expect(seed.errors).to be_nil
+ end
+ end
+
+ context 'when attributes are not valid' do
+ it 'returns the errors' do
+ stages_attributes[0][:builds] << {
+ name: 'invalid_job',
+ scheduling_type: :dag,
+ needs_attributes: [{ name: 'non-existent', artifacts: true }]
+ }
+
+ expect(seed.errors).to contain_exactly("invalid_job: needs 'non-existent'")
+ end
+ end
+ end
+
+ describe '#deployments_count' do
+ it 'counts the jobs having an environment associated' do
+ expect(seed.deployments_count).to eq(2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
index 650ae41320b..ade0e36cf1e 100644
--- a/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/accessibility_reports_comparer_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
- let(:comparer) { described_class.new(base_reports, head_reports) }
- let(:base_reports) { Gitlab::Ci::Reports::AccessibilityReports.new }
- let(:head_reports) { Gitlab::Ci::Reports::AccessibilityReports.new }
+ let(:comparer) { described_class.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::AccessibilityReports.new }
let(:url) { "https://gitlab.com" }
let(:single_error) do
[
@@ -38,233 +38,254 @@ RSpec.describe Gitlab::Ci::Reports::AccessibilityReportsComparer do
end
describe '#status' do
- subject { comparer.status }
+ subject(:status) { comparer.status }
context 'when head report has an error' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns status failed' do
- expect(subject).to eq(described_class::STATUS_FAILED)
+ expect(status).to eq(described_class::STATUS_FAILED)
end
end
context 'when head reports does not have errors' do
before do
- head_reports.add_url(url, [])
+ head_report.add_url(url, [])
end
it 'returns status success' do
- expect(subject).to eq(described_class::STATUS_SUCCESS)
+ expect(status).to eq(described_class::STATUS_SUCCESS)
end
end
end
describe '#errors_count' do
- subject { comparer.errors_count }
+ subject(:errors_count) { comparer.errors_count }
context 'when head report has an error' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns the number of new errors' do
- expect(subject).to eq(1)
+ expect(errors_count).to eq(1)
end
end
context 'when head reports does not have an error' do
before do
- head_reports.add_url(url, [])
+ head_report.add_url(url, [])
end
it 'returns the number new errors' do
- expect(subject).to eq(0)
+ expect(errors_count).to eq(0)
end
end
end
describe '#resolved_count' do
- subject { comparer.resolved_count }
+ subject(:resolved_count) { comparer.resolved_count }
context 'when base reports has an error and head has a different error' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, different_error)
end
it 'returns the resolved count' do
- expect(subject).to eq(1)
+ expect(resolved_count).to eq(1)
end
end
context 'when base reports has errors head has no errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, [])
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, [])
end
it 'returns the resolved count' do
- expect(subject).to eq(1)
+ expect(resolved_count).to eq(1)
end
end
context 'when base reports has errors and head has the same error' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, single_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns zero' do
- expect(subject).to eq(0)
+ expect(resolved_count).to eq(0)
end
end
context 'when base reports does not have errors and head has errors' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns the number of resolved errors' do
- expect(subject).to eq(0)
+ expect(resolved_count).to eq(0)
end
end
end
describe '#total_count' do
- subject { comparer.total_count }
+ subject(:total_count) { comparer.total_count }
context 'when base reports has an error' do
before do
- base_reports.add_url(url, single_error)
+ base_report.add_url(url, single_error)
end
- it 'returns the error count' do
- expect(subject).to eq(1)
+ it 'returns zero' do
+ expect(total_count).to be_zero
end
end
context 'when head report has an error' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
- it 'returns the error count' do
- expect(subject).to eq(1)
+ it 'returns the total count' do
+ expect(total_count).to eq(1)
end
end
context 'when base report has errors and head report has errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, different_error)
+ end
+
+ it 'returns the total count' do
+ expect(total_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head report has the same error' do
+ before do
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error + different_error)
end
- it 'returns the error count' do
- expect(subject).to eq(2)
+ it 'returns the total count' do
+ expect(total_count).to eq(2)
end
end
end
describe '#existing_errors' do
- subject { comparer.existing_errors }
+ subject(:existing_errors) { comparer.existing_errors }
context 'when base report has errors and head has a different error' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, different_error)
end
- it 'returns the existing errors' do
- expect(subject.size).to eq(1)
- expect(subject.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
end
end
context 'when base report does not have errors and head has errors' do
before do
- base_reports.add_url(url, [])
- head_reports.add_url(url, single_error)
+ base_report.add_url(url, [])
+ head_report.add_url(url, single_error)
end
it 'returns an empty array' do
- expect(subject).to be_empty
+ expect(existing_errors).to be_empty
+ end
+ end
+
+ context 'when base report has errors and head report has the same error' do
+ before do
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error + different_error)
+ end
+
+ it 'returns the existing error' do
+ expect(existing_errors).to eq(single_error)
end
end
end
describe '#new_errors' do
- subject { comparer.new_errors }
+ subject(:new_errors) { comparer.new_errors }
context 'when base reports has errors and head has more errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, single_error + different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error + different_error)
end
it 'returns new errors between base and head reports' do
- expect(subject.size).to eq(1)
- expect(subject.first["code"]).to eq("WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail")
+ expect(new_errors.size).to eq(1)
+ expect(new_errors.first["code"]).to eq("WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail")
end
end
context 'when base reports has an error and head has no errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, [])
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, [])
end
it 'returns an empty array' do
- expect(subject).to be_empty
+ expect(new_errors).to be_empty
end
end
context 'when base reports does not have errors and head has errors' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns the new error' do
- expect(subject.size).to eq(1)
- expect(subject.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
+ expect(new_errors.size).to eq(1)
+ expect(new_errors.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
end
end
end
describe '#resolved_errors' do
- subject { comparer.resolved_errors }
+ subject(:resolved_errors) { comparer.resolved_errors }
context 'when base report has errors and head has more errors' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, single_error + different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, single_error + different_error)
end
it 'returns an empty array' do
- expect(subject).to be_empty
+ expect(resolved_errors).to be_empty
end
end
context 'when base reports has errors and head has a different error' do
before do
- base_reports.add_url(url, single_error)
- head_reports.add_url(url, different_error)
+ base_report.add_url(url, single_error)
+ head_report.add_url(url, different_error)
end
it 'returns the resolved errors' do
- expect(subject.size).to eq(1)
- expect(subject.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
+ expect(resolved_errors.size).to eq(1)
+ expect(resolved_errors.first["code"]).to eq("WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent")
end
end
context 'when base reports does not have errors and head has errors' do
before do
- head_reports.add_url(url, single_error)
+ head_report.add_url(url, single_error)
end
it 'returns an empty array' do
- expect(subject).to be_empty
+ expect(resolved_errors).to be_empty
end
end
end
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
new file mode 100644
index 00000000000..7053d54381b
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
@@ -0,0 +1,308 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
+ let(:comparer) { described_class.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ describe '#status' do
+ subject(:report_status) { comparer.status }
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns status failed' do
+ expect(report_status).to eq(described_class::STATUS_FAILED)
+ end
+ end
+
+ context 'when head report does not have errors' do
+ it 'returns status success' do
+ expect(report_status).to eq(described_class::STATUS_SUCCESS)
+ end
+ end
+ end
+
+ describe '#errors_count' do
+ subject(:errors_count) { comparer.errors_count }
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns the number of new errors' do
+ expect(errors_count).to eq(1)
+ end
+ end
+
+ context 'when head report does not have an error' do
+ it 'returns zero' do
+ expect(errors_count).to be_zero
+ end
+ end
+ end
+
+ describe '#resolved_count' do
+ subject(:resolved_count) { comparer.resolved_count }
+
+ context 'when base report has an error and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'counts the base report error as resolved' do
+ expect(resolved_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors head has no errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'counts the base report errors as resolved' do
+ expect(resolved_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(resolved_count).to eq(0)
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(resolved_count).to be_zero
+ end
+ end
+ end
+
+ describe '#total_count' do
+ subject(:total_count) { comparer.total_count }
+
+ context 'when base report has an error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'returns zero' do
+ expect(total_count).to be_zero
+ end
+ end
+
+ context 'when head report has an error' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'includes the head report error in the count' do
+ expect(total_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head report has errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors in the count' do
+ expect(total_count).to eq(1)
+ end
+ end
+
+ context 'when base report has errors and head report has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors in the count' do
+ expect(total_count).to eq(2)
+ end
+ end
+ end
+
+ describe '#existing_errors' do
+ subject(:existing_errors) { comparer.existing_errors }
+
+ context 'when base report has errors and head has the same error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes the base report errors' do
+ expect(existing_errors).to contain_exactly(degradation_1)
+ end
+ end
+
+ context 'when base report has errors and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(existing_errors).to be_empty
+ end
+ end
+ end
+
+ describe '#new_errors' do
+ subject(:new_errors) { comparer.new_errors }
+
+ context 'when base report has errors and head has more errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'includes errors not found in the base report' do
+ expect(new_errors).to eq([degradation_2])
+ end
+ end
+
+ context 'when base report has an error and head has no errors' do
+ before do
+ base_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(new_errors).to be_empty
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns the head report error' do
+ expect(new_errors).to eq([degradation_1])
+ end
+ end
+ end
+
+ describe '#resolved_errors' do
+ subject(:resolved_errors) { comparer.resolved_errors }
+
+ context 'when base report errors are still found in the head report' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns an empty array' do
+ expect(resolved_errors).to be_empty
+ end
+ end
+
+ context 'when base report has errors and head has a different error' do
+ before do
+ base_report.add_degradation(degradation_1)
+ head_report.add_degradation(degradation_2)
+ end
+
+ it 'returns the base report error' do
+ expect(resolved_errors).to eq([degradation_1])
+ end
+ end
+
+ context 'when base report does not have errors and head has errors' do
+ before do
+ head_report.add_degradation(degradation_1)
+ end
+
+ it 'returns an empty array' do
+ expect(resolved_errors).to be_empty
+ 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
new file mode 100644
index 00000000000..44e67259369
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
+ let(:codequality_report) { described_class.new }
+ let(:degradation_1) do
+ {
+ "categories": [
+ "Complexity"
+ ],
+ "check_name": "argument_count",
+ "content": {
+ "body": ""
+ },
+ "description": "Method `new_array` has 12 arguments (exceeds 4 allowed). Consider refactoring.",
+ "fingerprint": "15cdb5c53afd42bc22f8ca366a08d547",
+ "location": {
+ "path": "foo.rb",
+ "lines": {
+ "begin": 10,
+ "end": 10
+ }
+ },
+ "other_locations": [],
+ "remediation_points": 900000,
+ "severity": "major",
+ "type": "issue",
+ "engine_name": "structure"
+ }.with_indifferent_access
+ end
+
+ let(:degradation_2) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "categories": [
+ "Complexity"
+ ],
+ "remediation_points": 550000,
+ "location": {
+ "path": "foo.rb",
+ "positions": {
+ "begin": {
+ "column": 14,
+ "line": 10
+ },
+ "end": {
+ "column": 39,
+ "line": 10
+ }
+ }
+ },
+ "content": {
+ "body": "This cop checks for methods with too many parameters.\nThe maximum number of parameters is configurable.\nKeyword arguments can optionally be excluded from the total count."
+ },
+ "engine_name": "rubocop",
+ "fingerprint": "ab5f8b935886b942d621399f5a2ca16e",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ 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)
+ end
+
+ it 'adds degradation to codequality report' do
+ expect(codequality_report.degradations.keys).to eq([degradation_1[:fingerprint]])
+ expect(codequality_report.degradations.values.size).to eq(1)
+ end
+ end
+
+ context 'when a required property is missing in the degradation' do
+ let(:invalid_degradation) do
+ {
+ "type": "Issue",
+ "check_name": "Rubocop/Metrics/ParameterLists",
+ "description": "Avoid parameter lists longer than 5 parameters. [12/5]",
+ "fingerprint": "ab5f8b935886b942d621399aefkaehfiaehf",
+ "severity": "minor"
+ }.with_indifferent_access
+ end
+
+ it 'sets location as an error' do
+ codequality_report.add_degradation(invalid_degradation)
+
+ expect(codequality_report.error_message).to eq("Invalid degradation format: The property '#/' did not contain a required property of 'location'")
+ end
+ end
+ end
+
+ describe '#set_error_message' do
+ context 'when there is an error' do
+ it 'sets errors' do
+ codequality_report.set_error_message("error")
+
+ expect(codequality_report.error_message).to eq("error")
+ end
+ end
+ end
+
+ describe '#degradations_count' do
+ subject(:degradations_count) { codequality_report.degradations_count }
+
+ context 'when there are many degradations' do
+ before do
+ codequality_report.add_degradation(degradation_1)
+ codequality_report.add_degradation(degradation_2)
+ end
+
+ it 'returns the number of degradations' do
+ expect(degradations_count).to eq(2)
+ end
+ end
+ end
+
+ describe '#all_degradations' do
+ subject(:all_degradations) { codequality_report.all_degradations }
+
+ context 'when there are many degradations' do
+ before do
+ codequality_report.add_degradation(degradation_1)
+ codequality_report.add_degradation(degradation_2)
+ end
+
+ it 'returns all degradations' do
+ expect(all_degradations).to contain_exactly(degradation_1, degradation_2)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb
new file mode 100644
index 00000000000..1e5e4766583
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/reports_comparer_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::ReportsComparer do
+ let(:comparer) { described_class.new(base_report, head_report) }
+ let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+ let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
+
+ describe '#initialize' do
+ context 'sets getter for the report comparer' do
+ it 'return base report' do
+ expect(comparer.base_report).to be_an_instance_of(Gitlab::Ci::Reports::CodequalityReports)
+ end
+
+ it 'return head report' do
+ expect(comparer.head_report).to be_an_instance_of(Gitlab::Ci::Reports::CodequalityReports)
+ end
+ end
+ end
+
+ describe '#status' do
+ subject(:status) { comparer.status }
+
+ it 'returns not implemented error' do
+ expect { status }.to raise_error(NotImplementedError)
+ end
+
+ context 'when success? is true' do
+ before do
+ allow(comparer).to receive(:success?).and_return(true)
+ end
+
+ it 'returns status success' do
+ expect(status).to eq('success')
+ end
+ end
+
+ context 'when success? is false' do
+ before do
+ allow(comparer).to receive(:success?).and_return(false)
+ end
+
+ it 'returns status failed' do
+ expect(status).to eq('failed')
+ end
+ end
+ end
+
+ describe '#success?' do
+ subject(:success?) { comparer.success? }
+
+ it 'returns not implemented error' do
+ expect { success? }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#existing_errors' do
+ subject(:existing_errors) { comparer.existing_errors }
+
+ it 'returns not implemented error' do
+ expect { existing_errors }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#resolved_errors' do
+ subject(:resolved_errors) { comparer.resolved_errors }
+
+ it 'returns not implemented error' do
+ expect { resolved_errors }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#errors_count' do
+ subject(:errors_count) { comparer.errors_count }
+
+ it 'returns not implemented error' do
+ expect { errors_count }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#resolved_count' do
+ subject(:resolved_count) { comparer.resolved_count }
+
+ it 'returns not implemented error' do
+ expect { resolved_count }.to raise_error(NotImplementedError)
+ end
+ end
+
+ describe '#total_count' do
+ subject(:total_count) { comparer.total_count }
+
+ it 'returns not implemented error' do
+ expect { total_count }.to raise_error(NotImplementedError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/npm_spec.rb b/spec/lib/gitlab/ci/templates/npm_spec.rb
new file mode 100644
index 00000000000..1f8e32ce019
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/npm_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'npm.latest.gitlab-ci.yml' do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('npm.latest') }
+
+ describe 'the created pipeline' do
+ let_it_be(:user) { create(:admin) }
+
+ let(:repo_files) { { 'package.json' => '{}', 'README.md' => '' } }
+ let(:modified_files) { %w[package.json] }
+ let(:project) { create(:project, :custom_repo, files: repo_files) }
+ let(:pipeline_branch) { project.default_branch }
+ let(:pipeline_tag) { 'v1.2.1' }
+ let(:pipeline_ref) { pipeline_branch }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref ) }
+ let(:pipeline) { service.execute!(:push) }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ def create_branch(name:)
+ ::Branches::CreateService.new(project, user).execute(name, project.default_branch)
+ end
+
+ def create_tag(name:)
+ ::Tags::CreateService.new(project, user).execute(name, project.default_branch, nil)
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+
+ create_branch(name: pipeline_branch)
+ create_tag(name: pipeline_tag)
+
+ allow_any_instance_of(Ci::Pipeline).to receive(:modified_paths).and_return(modified_files)
+ end
+
+ shared_examples 'publish job created' do
+ it 'creates a pipeline with a single job: publish' do
+ expect(build_names).to eq(%w[publish])
+ end
+ end
+
+ shared_examples 'no pipeline created' do
+ it 'does not create a pipeline because the only job (publish) is not created' do
+ expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError, 'No stages / jobs for this pipeline.')
+ end
+ end
+
+ context 'on default branch' do
+ context 'when package.json has been changed' do
+ it_behaves_like 'publish job created'
+ end
+
+ context 'when package.json does not exist or has not been changed' do
+ let(:modified_files) { %w[README.md] }
+
+ it_behaves_like 'no pipeline created'
+ end
+ end
+
+ %w[v1.0.0 v2.1.0-alpha].each do |valid_version|
+ context "when the branch name is #{valid_version}" do
+ let(:pipeline_branch) { valid_version }
+
+ it_behaves_like 'publish job created'
+ end
+
+ context "when the tag name is #{valid_version}" do
+ let(:pipeline_tag) { valid_version }
+ let(:pipeline_ref) { pipeline_tag }
+
+ it_behaves_like 'publish job created'
+ end
+ end
+
+ %w[patch-1 my-feature-branch v1 v1.0 2.1.0].each do |invalid_version|
+ context "when the branch name is #{invalid_version}" do
+ let(:pipeline_branch) { invalid_version }
+
+ it_behaves_like 'no pipeline created'
+ end
+
+ context "when the tag name is #{invalid_version}" do
+ let(:pipeline_tag) { invalid_version }
+ let(:pipeline_ref) { pipeline_tag }
+
+ it_behaves_like 'no pipeline created'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/trace/checksum_spec.rb b/spec/lib/gitlab/ci/trace/checksum_spec.rb
index 794794c3f69..a343d74f755 100644
--- a/spec/lib/gitlab/ci/trace/checksum_spec.rb
+++ b/spec/lib/gitlab/ci/trace/checksum_spec.rb
@@ -8,8 +8,12 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
subject { described_class.new(build) }
context 'when build pending state exists' do
+ let(:trace_details) do
+ { trace_checksum: 'crc32:d4777540', trace_bytesize: 262161 }
+ end
+
before do
- create(:ci_build_pending_state, build: build, trace_checksum: 'crc32:d4777540')
+ create(:ci_build_pending_state, build: build, **trace_details)
end
context 'when matching persisted trace chunks exist' do
@@ -22,6 +26,7 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
it 'calculates combined trace chunks CRC32 correctly' do
expect(subject.chunks_crc32).to eq 3564598592
expect(subject).to be_valid
+ expect(subject).not_to be_corrupted
end
end
@@ -32,8 +37,9 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
create_chunk(index: 2, data: 'ccccccccccccccccc')
end
- it 'makes trace checksum invalid' do
+ it 'makes trace checksum invalid but not corrupted' do
expect(subject).not_to be_valid
+ expect(subject).not_to be_corrupted
end
end
@@ -43,8 +49,9 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
create_chunk(index: 2, data: 'ccccccccccccccccc')
end
- it 'makes trace checksum invalid' do
+ it 'makes trace checksum invalid and corrupted' do
expect(subject).not_to be_valid
+ expect(subject).to be_corrupted
end
end
@@ -55,8 +62,9 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
create_chunk(index: 2, data: 'ccccccccccccccccc')
end
- it 'makes trace checksum invalid' do
+ it 'makes trace checksum invalid but not corrupted' do
expect(subject).not_to be_valid
+ expect(subject).not_to be_corrupted
end
end
@@ -99,6 +107,14 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
it 'returns nil' do
expect(subject.last_chunk).to be_nil
end
+
+ it 'is not a valid trace' do
+ expect(subject).not_to be_valid
+ end
+
+ it 'is not a corrupted trace' do
+ expect(subject).not_to be_corrupted
+ end
end
context 'when there are multiple chunks' do
@@ -110,6 +126,26 @@ RSpec.describe Gitlab::Ci::Trace::Checksum do
it 'returns chunk with the highest index' do
expect(subject.last_chunk.chunk_index).to eq 1
end
+
+ it 'is not a valid trace' do
+ expect(subject).not_to be_valid
+ end
+
+ it 'is not a corrupted trace' do
+ expect(subject).not_to be_corrupted
+ end
+ end
+ end
+
+ describe '#trace_size' do
+ before do
+ create_chunk(index: 0, data: 'a' * 128.kilobytes)
+ create_chunk(index: 1, data: 'b' * 128.kilobytes)
+ create_chunk(index: 2, data: 'abcdefg-ΓΌ')
+ end
+
+ it 'returns total trace size in bytes' do
+ expect(subject.trace_size).to eq 262154
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index fb6395e888a..5ad1b3dd241 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -231,6 +231,23 @@ module Gitlab
expect(subject[:allow_failure]).to be true
end
end
+
+ context 'when allow_failure has exit_codes' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec',
+ when: 'manual',
+ allow_failure: { exit_codes: 1 } })
+ end
+
+ it 'is not allowed to fail' do
+ expect(subject[:allow_failure]).to be false
+ end
+
+ it 'saves allow_failure_criteria into options' do
+ expect(subject[:options]).to match(
+ a_hash_including(allow_failure_criteria: { exit_codes: [1] }))
+ end
+ end
end
context 'when job is not a manual action' do
@@ -254,6 +271,22 @@ module Gitlab
expect(subject[:allow_failure]).to be false
end
end
+
+ context 'when allow_failure is dynamically specified' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec',
+ allow_failure: { exit_codes: 1 } })
+ end
+
+ it 'is not allowed to fail' do
+ expect(subject[:allow_failure]).to be false
+ end
+
+ it 'saves allow_failure_criteria into options' do
+ expect(subject[:options]).to match(
+ a_hash_including(allow_failure_criteria: { exit_codes: [1] }))
+ end
+ end
end
end
@@ -2111,6 +2144,71 @@ module Gitlab
end
end
+ describe 'cross pipeline needs' do
+ context 'when configuration is valid' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ stage: test
+ script: rspec
+ needs:
+ - pipeline: $THE_PIPELINE_ID
+ job: dependency-job
+ YAML
+ end
+
+ it 'returns a valid configuration and sets artifacts: true by default' do
+ expect(subject).to be_valid
+
+ rspec = subject.build_attributes(:rspec)
+ expect(rspec.dig(:options, :cross_dependencies)).to eq(
+ [{ pipeline: '$THE_PIPELINE_ID', job: 'dependency-job', artifacts: true }]
+ )
+ end
+
+ context 'when pipeline ID is hard-coded' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ stage: test
+ script: rspec
+ needs:
+ - pipeline: "123"
+ job: dependency-job
+ YAML
+ end
+
+ it 'returns a valid configuration and sets artifacts: true by default' do
+ expect(subject).to be_valid
+
+ rspec = subject.build_attributes(:rspec)
+ expect(rspec.dig(:options, :cross_dependencies)).to eq(
+ [{ pipeline: '123', job: 'dependency-job', artifacts: true }]
+ )
+ end
+ end
+ end
+
+ context 'when configuration is not valid' do
+ let(:config) do
+ <<~YAML
+ rspec:
+ stage: test
+ script: rspec
+ needs:
+ - pipeline: $THE_PIPELINE_ID
+ job: dependency-job
+ something: else
+ YAML
+ end
+
+ it 'returns an error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors).to include(/:need config contains unknown keys: something/)
+ end
+ end
+ end
+
describe "Hidden jobs" do
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config).execute }
@@ -2429,7 +2527,13 @@ module Gitlab
context 'returns errors if job allow_failure parameter is not an boolean' do
let(:config) { YAML.dump({ rspec: { script: "test", allow_failure: "string" } }) }
- it_behaves_like 'returns errors', 'jobs:rspec allow failure should be a boolean value'
+ it_behaves_like 'returns errors', 'jobs:rspec allow failure should be a hash or a boolean value'
+ end
+
+ context 'returns errors if job exit_code parameter from allow_failure is not an integer' do
+ let(:config) { YAML.dump({ rspec: { script: "test", allow_failure: { exit_codes: 'string' } } }) }
+
+ it_behaves_like 'returns errors', 'jobs:rspec:allow_failure exit codes should be an array of integers or an integer'
end
context 'returns errors if job stage is not a string' do