diff options
Diffstat (limited to 'spec/lib/gitlab/ci')
38 files changed, 1224 insertions, 242 deletions
diff --git a/spec/lib/gitlab/ci/ansi2json/line_spec.rb b/spec/lib/gitlab/ci/ansi2json/line_spec.rb index 8b1cd812a70..d681447a0e8 100644 --- a/spec/lib/gitlab/ci/ansi2json/line_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json/line_spec.rb @@ -58,6 +58,15 @@ RSpec.describe Gitlab::Ci::Ansi2json::Line do end end + describe '#set_section_options' do + it 'sets the current section\'s options' do + options = { collapsed: true } + subject.set_section_options(options) + + expect(subject.to_h[:section_options]).to eq(options) + end + end + describe '#set_as_section_header' do it 'change the section_header to true' do expect { subject.set_as_section_header } diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb index cb6949fddc2..c9c0d1a744e 100644 --- a/spec/lib/gitlab/ci/ansi2json_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json_spec.rb @@ -229,7 +229,7 @@ RSpec.describe Gitlab::Ci::Ansi2json do expect(convert_json(trace)).to eq([ { offset: 0, - content: [{ text: "section_end:1:2<div>hello</div>" }], + content: [{ text: 'section_end:1:2<div>hello</div>' }], section: 'prepare-script', section_header: true }, @@ -329,6 +329,32 @@ RSpec.describe Gitlab::Ci::Ansi2json do ]) end end + + context 'with section options' do + let(:option_section_start) { "section_start:#{section_start_time.to_i}:#{section_name}[collapsed=true,unused_option=123]\r\033[0K"} + + it 'provides section options when set' do + trace = "#{option_section_start}hello#{section_end}" + expect(convert_json(trace)).to eq([ + { + offset: 0, + content: [{ text: 'hello' }], + section: 'prepare-script', + section_header: true, + section_options: { + 'collapsed' => 'true', + 'unused_option' => '123' + } + }, + { + offset: 83, + content: [], + section: 'prepare-script', + section_duration: '01:03' + } + ]) + end + end end describe 'incremental updates' do @@ -339,7 +365,7 @@ RSpec.describe Gitlab::Ci::Ansi2json do context 'with split word' do let(:pre_text) { "\e[1mHello " } - let(:text) { "World" } + let(:text) { 'World' } let(:lines) do [ @@ -355,7 +381,7 @@ RSpec.describe Gitlab::Ci::Ansi2json do context 'with split word on second line' do let(:pre_text) { "Good\nmorning " } - let(:text) { "World" } + let(:text) { 'World' } let(:lines) do [ @@ -514,7 +540,7 @@ RSpec.describe Gitlab::Ci::Ansi2json do end describe 'trucates' do - let(:text) { "Hello World" } + let(:text) { 'Hello World' } let(:stream) { StringIO.new(text) } let(:subject) { described_class.convert(stream) } @@ -522,11 +548,11 @@ RSpec.describe Gitlab::Ci::Ansi2json do stream.seek(3, IO::SEEK_SET) end - it "returns truncated output" do + it 'returns truncated output' do expect(subject.truncated).to be_truthy end - it "does not append output" do + it 'does not append output' do expect(subject.append).to be_falsey end end diff --git a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb index 83a37655ea9..e982f0eb015 100644 --- a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb +++ b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb @@ -18,17 +18,6 @@ RSpec.describe Gitlab::Ci::ArtifactFileReader do expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom') end - context 'when FF ci_new_artifact_file_reader is disabled' do - before do - stub_feature_flags(ci_new_artifact_file_reader: false) - end - - it 'returns the content at the path' do - is_expected.to be_present - expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom') - end - end - context 'when path does not exist' do let(:path) { 'file/does/not/exist.txt' } let(:expected_error) do diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb index f33176c3da3..8b2e0410474 100644 --- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb @@ -228,4 +228,66 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do end end end + + describe '#manual_action?' do + context 'when job is a manual action' do + let(:config) { { script: 'deploy', when: 'manual' } } + + it { is_expected.to be_manual_action } + end + + context 'when job is not a manual action' do + let(:config) { { script: 'deploy' } } + + it { is_expected.not_to be_manual_action } + end + end + + describe '#ignored?' do + context 'when job is a manual action' do + context 'when it is not specified if job is allowed to fail' do + let(:config) do + { script: 'deploy', when: 'manual' } + end + + it { is_expected.to be_ignored } + end + + context 'when job is allowed to fail' do + let(:config) do + { script: 'deploy', when: 'manual', allow_failure: true } + end + + it { is_expected.to be_ignored } + end + + context 'when job is not allowed to fail' do + let(:config) do + { script: 'deploy', when: 'manual', allow_failure: false } + end + + it { is_expected.not_to be_ignored } + end + end + + context 'when job is not a manual action' do + context 'when it is not specified if job is allowed to fail' do + let(:config) { { script: 'deploy' } } + + it { is_expected.not_to be_ignored } + end + + context 'when job is allowed to fail' do + let(:config) { { script: 'deploy', allow_failure: true } } + + it { is_expected.to be_ignored } + end + + context 'when job is not allowed to fail' do + let(:config) { { script: 'deploy', allow_failure: false } } + + it { is_expected.not_to be_ignored } + end + end + end end diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb index 3501812b76e..80427eaa6ee 100644 --- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb @@ -13,18 +13,23 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do context 'when entry config value is correct' do let(:policy) { nil } let(:key) { 'some key' } + let(:when_config) { nil } let(:config) do - { key: key, + { + key: key, untracked: true, - paths: ['some/path/'], - policy: policy } + paths: ['some/path/'] + }.tap do |config| + config[:policy] = policy if policy + config[:when] = when_config if when_config + end end describe '#value' do shared_examples 'hash key value' do it 'returns hash value' do - expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push') + expect(entry.value).to eq(key: key, untracked: true, paths: ['some/path/'], policy: 'pull-push', when: 'on_success') end end @@ -49,6 +54,48 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do expect(entry.value).to match(a_hash_including(key: nil)) end end + + context 'with `policy`' do + using RSpec::Parameterized::TableSyntax + + where(:policy, :result) do + 'pull-push' | 'pull-push' + 'push' | 'push' + 'pull' | 'pull' + 'unknown' | 'unknown' # invalid + end + + with_them do + it { expect(entry.value).to include(policy: result) } + end + end + + context 'without `policy`' do + it 'assigns policy to default' do + expect(entry.value).to include(policy: 'pull-push') + end + end + + context 'with `when`' do + using RSpec::Parameterized::TableSyntax + + where(:when_config, :result) do + 'on_success' | 'on_success' + 'on_failure' | 'on_failure' + 'always' | 'always' + 'unknown' | 'unknown' # invalid + end + + with_them do + it { expect(entry.value).to include(when: result) } + end + end + + context 'without `when`' do + it 'assigns when to default' do + expect(entry.value).to include(when: 'on_success') + end + end end describe '#valid?' do @@ -61,28 +108,41 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do end end - context 'policy is pull-push' do - let(:policy) { 'pull-push' } + context 'with `policy`' do + using RSpec::Parameterized::TableSyntax - it { is_expected.to be_valid } - it { expect(entry.value).to include(policy: 'pull-push') } - end - - context 'policy is push' do - let(:policy) { 'push' } + where(:policy, :valid) do + 'pull-push' | true + 'push' | true + 'pull' | true + 'unknown' | false + end - it { is_expected.to be_valid } - it { expect(entry.value).to include(policy: 'push') } + with_them do + it 'returns expected validity' do + expect(entry.valid?).to eq(valid) + end + end end - context 'policy is pull' do - let(:policy) { 'pull' } + context 'with `when`' do + using RSpec::Parameterized::TableSyntax - it { is_expected.to be_valid } - it { expect(entry.value).to include(policy: 'pull') } + where(:when_config, :valid) do + 'on_success' | true + 'on_failure' | true + 'always' | true + 'unknown' | false + end + + with_them do + it 'returns expected validity' do + expect(entry.valid?).to eq(valid) + end + end end - context 'when key is missing' do + context 'with key missing' do let(:config) do { untracked: true, paths: ['some/path/'] } @@ -110,13 +170,21 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do end context 'when policy is unknown' do - let(:config) { { policy: "unknown" } } + let(:config) { { policy: 'unknown' } } it 'reports error' do is_expected.to include('cache policy should be pull-push, push, or pull') end end + context 'when `when` is unknown' do + let(:config) { { when: 'unknown' } } + + it 'reports error' do + is_expected.to include('cache when should be on_success, on_failure or always') + end + end + context 'when descendants are invalid' do context 'with invalid keys' do let(:config) { { key: 1 } } diff --git a/spec/lib/gitlab/ci/config/entry/include_spec.rb b/spec/lib/gitlab/ci/config/entry/include_spec.rb index 3e816f70c03..59f0b0e7a48 100644 --- a/spec/lib/gitlab/ci/config/entry/include_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/include_spec.rb @@ -61,6 +61,31 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Include do end end end + + context 'when using "project"' do + context 'and specifying "ref" and "file"' do + let(:config) { { project: 'my-group/my-pipeline-library', ref: 'master', file: 'test.yml' } } + + it { is_expected.to be_valid } + end + + context 'without "ref"' do + let(:config) { { project: 'my-group/my-pipeline-library', file: 'test.yml' } } + + it { is_expected.to be_valid } + end + + context 'without "file"' do + let(:config) { { project: 'my-group/my-pipeline-library' } } + + it { is_expected.not_to be_valid } + + it 'has specific error' do + expect(include_entry.errors) + .to include('include config must specify the file where to fetch the config from') + end + end + end end context 'when value is something else' do diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index ab760b107f8..e0e8bc93770 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -537,7 +537,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it 'overrides default config' do expect(entry[:image].value).to eq(name: 'some_image') - expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push') + expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success') end end @@ -552,7 +552,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do it 'uses config from default entry' do expect(entry[:image].value).to eq 'specified' - expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push') + expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push', when: 'on_success') end end diff --git a/spec/lib/gitlab/ci/config/entry/product/matrix_spec.rb b/spec/lib/gitlab/ci/config/entry/product/matrix_spec.rb index 39697884e3b..3388ae0af2f 100644 --- a/spec/lib/gitlab/ci/config/entry/product/matrix_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/product/matrix_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' require_dependency 'active_model' RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Matrix do @@ -46,33 +46,140 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Matrix do end end - context 'when entry config has only one variable' do - let(:config) do - [ - { - 'VAR_1' => %w[test] - } - ] + context 'with one_dimensional_matrix feature flag enabled' do + before do + stub_feature_flags(one_dimensional_matrix: true) + matrix.compose! end - describe '#valid?' do - it { is_expected.not_to be_valid } - end + context 'when entry config has only one variable with multiple values' do + let(:config) do + [ + { + 'VAR_1' => %w[build test] + } + ] + end - describe '#errors' do - it 'returns error about too many jobs' do - expect(matrix.errors) - .to include('variables config requires at least 2 items') + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#errors' do + it 'returns no errors' do + expect(matrix.errors) + .to be_empty + end + end + + describe '#value' do + before do + matrix.compose! + end + + it 'returns the value without raising an error' do + expect(matrix.value).to eq([{ 'VAR_1' => %w[build test] }]) + end end + + context 'when entry config has only one variable with one value' do + let(:config) do + [ + { + 'VAR_1' => %w[test] + } + ] + end + + describe '#valid?' do + it { is_expected.to be_valid } + end + + describe '#errors' do + it 'returns no errors' do + expect(matrix.errors) + .to be_empty + end + end + + describe '#value' do + before do + matrix.compose! + end + + it 'returns the value without raising an error' do + expect(matrix.value).to eq([{ 'VAR_1' => %w[test] }]) + end + end + end + end + end + + context 'with one_dimensional_matrix feature flag disabled' do + before do + stub_feature_flags(one_dimensional_matrix: false) + matrix.compose! end - describe '#value' do - before do - matrix.compose! + context 'when entry config has only one variable with multiple values' do + let(:config) do + [ + { + 'VAR_1' => %w[build test] + } + ] end - it 'returns the value without raising an error' do - expect(matrix.value).to eq([{ 'VAR_1' => ['test'] }]) + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'returns error about too many jobs' do + expect(matrix.errors) + .to include('variables config requires at least 2 items') + end + end + + describe '#value' do + before do + matrix.compose! + end + + it 'returns the value without raising an error' do + expect(matrix.value).to eq([{ 'VAR_1' => %w[build test] }]) + end + end + + context 'when entry config has only one variable with one value' do + let(:config) do + [ + { + 'VAR_1' => %w[test] + } + ] + end + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + + describe '#errors' do + it 'returns no errors' do + expect(matrix.errors) + .to include('variables config requires at least 2 items') + end + end + + describe '#value' do + before do + matrix.compose! + end + + it 'returns the value without raising an error' do + expect(matrix.value).to eq([{ 'VAR_1' => %w[test] }]) + end + end end end end diff --git a/spec/lib/gitlab/ci/config/entry/product/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/product/variables_spec.rb index 230b001d620..407efb438b5 100644 --- a/spec/lib/gitlab/ci/config/entry/product/variables_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/product/variables_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -require 'fast_spec_helper' +# After Feature one_dimensional_matrix is removed, this can be changed back to fast_spec_helper +require 'spec_helper' require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Product::Variables do @@ -45,43 +46,71 @@ RSpec.describe Gitlab::Ci::Config::Entry::Product::Variables do end end - context 'when entry value is not correct' do - shared_examples 'invalid variables' do |message| - describe '#errors' do - it 'saves errors' do - expect(entry.errors).to include(message) - end + context 'with one_dimensional_matrix feature flag enabled' do + context 'with only one variable' do + before do + stub_feature_flags(one_dimensional_matrix: true) end + let(:config) { { VAR: 'test' } } describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty end end end + end - context 'with array' do - let(:config) { [:VAR, 'test'] } + context 'with one_dimensional_matrix feature flag disabled' do + context 'when entry value is not correct' do + before do + stub_feature_flags(one_dimensional_matrix: false) + end + shared_examples 'invalid variables' do |message| + describe '#errors' do + it 'saves errors' do + expect(entry.errors).to include(message) + end + end - it_behaves_like 'invalid variables', /should be a hash of key value pairs/ - end + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end - context 'with empty array' do - let(:config) { { VAR: 'test', VAR2: [] } } + context 'with array' do + let(:config) { [:VAR, 'test'] } - it_behaves_like 'invalid variables', /should be a hash of key value pairs/ - end + it_behaves_like 'invalid variables', /should be a hash of key value pairs/ + end - context 'with nested array' do - let(:config) { { VAR: 'test', VAR2: [1, [2]] } } + context 'with empty array' do + let(:config) { { VAR: 'test', VAR2: [] } } - it_behaves_like 'invalid variables', /should be a hash of key value pairs/ - end + it_behaves_like 'invalid variables', /should be a hash of key value pairs/ + end - context 'with only one variable' do - let(:config) { { VAR: 'test' } } + context 'with nested array' do + let(:config) { { VAR: 'test', VAR2: [1, [2]] } } + + it_behaves_like 'invalid variables', /should be a hash of key value pairs/ + end - it_behaves_like 'invalid variables', /variables config requires at least 2 items/ + context 'with one_dimensional_matrix feature flag disabled' do + context 'with only one variable' do + let(:config) { { VAR: 'test' } } + + it_behaves_like 'invalid variables', /variables config requires at least 2 items/ + end + end end end end diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index 252bda6461d..79716df6b60 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -127,7 +127,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'ruby:2.7' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }, variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], @@ -141,7 +141,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'ruby:2.7' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }, variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], @@ -156,7 +156,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do release: { name: "Release $CI_TAG_NAME", tag_name: 'v0.06', description: "./release_changelog.txt" }, image: { name: "ruby:2.7" }, services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }], - cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push" }, + cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }, only: { refs: %w(branches tags) }, variables: { 'VAR' => 'job' }, after_script: [], @@ -203,7 +203,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'ruby:2.7' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }, variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], @@ -215,7 +215,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do image: { name: 'ruby:2.7' }, services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', - cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" }, + cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }, variables: { 'VAR' => 'job' }, ignore: false, after_script: ['make clean'], @@ -261,7 +261,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do describe '#cache_value' do it 'returns correct cache definition' do - expect(root.cache_value).to eq(key: 'a', policy: 'pull-push') + expect(root.cache_value).to eq(key: 'a', policy: 'pull-push', when: 'on_success') end end end diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb index d6391092f63..ac33f858f43 100644 --- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb @@ -3,56 +3,109 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Variables do - let(:entry) { described_class.new(config) } + subject { described_class.new(config) } - describe 'validations' do - context 'when entry config value is correct' do - let(:config) do - { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + shared_examples 'valid config' do + describe '#value' do + it 'returns hash with key value strings' do + expect(subject.value).to eq result end + end - describe '#value' do - it 'returns hash with key value strings' do - expect(entry.value).to eq config - end - - context 'with numeric keys and values in the config' do - let(:config) { { 10 => 20 } } + describe '#errors' do + it 'does not append errors' do + expect(subject.errors).to be_empty + end + end - it 'converts numeric key and numeric value into strings' do - expect(entry.value).to eq('10' => '20') - end - end + describe '#valid?' do + it 'is valid' do + expect(subject).to be_valid end + end + end - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end + shared_examples 'invalid config' do + describe '#valid?' do + it 'is not valid' do + expect(subject).not_to be_valid end + end - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end + describe '#errors' do + it 'saves errors' do + expect(subject.errors) + .to include /should be a hash of key value pairs/ end end + end - context 'when entry value is not correct' do - let(:config) { [:VAR, 'test'] } + context 'when entry config value has key-value pairs' do + let(:config) do + { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + end - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include /should be a hash of key value pairs/ - end - end + let(:result) do + { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + end - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid - end - end + it_behaves_like 'valid config' + end + + context 'with numeric keys and values in the config' do + let(:config) { { 10 => 20 } } + let(:result) do + { '10' => '20' } + end + + it_behaves_like 'valid config' + end + + context 'when entry config value has key-value pair and hash' do + let(:config) do + { 'VARIABLE_1' => { value: 'value 1', description: 'variable 1' }, + 'VARIABLE_2' => 'value 2' } + end + + let(:result) do + { 'VARIABLE_1' => 'value 1', 'VARIABLE_2' => 'value 2' } + end + + it_behaves_like 'valid config' + end + + context 'when entry value is an array' do + let(:config) { [:VAR, 'test'] } + + 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 + + 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 + + it_behaves_like 'invalid config' + end + + context 'when entry config value has hash without description' do + let(:config) do + { 'VARIABLE_1' => { value: 'value 1' } } + end + + let(:result) do + { 'VARIABLE_1' => 'value 1' } + end + + it_behaves_like 'valid config' end end diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index f724825a9cc..dd27b4045c9 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -82,7 +82,7 @@ RSpec.describe Gitlab::Ci::CronParser do context 'when PST (Pacific Standard Time)' do it 'converts time in server time zone' do - Timecop.freeze(Time.utc(2017, 1, 1)) do + travel_to(Time.utc(2017, 1, 1)) do expect(subject.hour).to eq(hour_in_utc) end end @@ -90,7 +90,7 @@ RSpec.describe Gitlab::Ci::CronParser do context 'when PDT (Pacific Daylight Time)' do it 'converts time in server time zone' do - Timecop.freeze(Time.utc(2017, 6, 1)) do + travel_to(Time.utc(2017, 6, 1)) do expect(subject.hour).to eq(hour_in_utc) end end @@ -117,7 +117,7 @@ RSpec.describe Gitlab::Ci::CronParser do context 'when CET (Central European Time)' do it 'converts time in server time zone' do - Timecop.freeze(Time.utc(2017, 1, 1)) do + travel_to(Time.utc(2017, 1, 1)) do expect(subject.hour).to eq(hour_in_utc) end end @@ -125,7 +125,7 @@ RSpec.describe Gitlab::Ci::CronParser do context 'when CEST (Central European Summer Time)' do it 'converts time in server time zone' do - Timecop.freeze(Time.utc(2017, 6, 1)) do + travel_to(Time.utc(2017, 6, 1)) do expect(subject.hour).to eq(hour_in_utc) end end @@ -152,7 +152,7 @@ RSpec.describe Gitlab::Ci::CronParser do context 'when EST (Eastern Standard Time)' do it 'converts time in server time zone' do - Timecop.freeze(Time.utc(2017, 1, 1)) do + travel_to(Time.utc(2017, 1, 1)) do expect(subject.hour).to eq(hour_in_utc) end end @@ -160,7 +160,7 @@ RSpec.describe Gitlab::Ci::CronParser do context 'when EDT (Eastern Daylight Time)' do it 'converts time in server time zone' do - Timecop.freeze(Time.utc(2017, 6, 1)) do + travel_to(Time.utc(2017, 6, 1)) do expect(subject.hour).to eq(hour_in_utc) end end @@ -174,7 +174,7 @@ RSpec.describe Gitlab::Ci::CronParser do # (e.g. America/Chicago) at the start of the test. Stubbing # TZ doesn't appear to be enough. it 'generates day without TZInfo::AmbiguousTime error' do - Timecop.freeze(Time.utc(2020, 1, 1)) do + travel_to(Time.utc(2020, 1, 1)) do expect(subject.year).to eq(year) expect(subject.month).to eq(12) expect(subject.day).to eq(1) diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb index 077c0fd3162..c67f8464123 100644 --- a/spec/lib/gitlab/ci/lint_spec.rb +++ b/spec/lib/gitlab/ci/lint_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Lint do - let_it_be(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let(:lint) { described_class.new(project: project, current_user: user) } @@ -61,6 +61,43 @@ RSpec.describe Gitlab::Ci::Lint do end end + shared_examples 'sets merged yaml' do + let(:content) do + <<~YAML + :include: + :local: another-gitlab-ci.yml + :test_job: + :stage: test + :script: echo + YAML + end + + let(:included_content) do + <<~YAML + :another_job: + :script: echo + YAML + end + + before do + project.repository.create_file( + project.creator, + 'another-gitlab-ci.yml', + included_content, + message: 'Automatically created another-gitlab-ci.yml', + branch_name: 'master' + ) + end + + it 'sets merged_config' do + root_config = YAML.safe_load(content, [Symbol]) + included_config = YAML.safe_load(included_content, [Symbol]) + expected_config = included_config.merge(root_config).except(:include) + + expect(subject.merged_yaml).to eq(expected_config.to_yaml) + end + end + shared_examples 'content with errors and warnings' do context 'when content has errors' do let(:content) do @@ -173,6 +210,8 @@ RSpec.describe Gitlab::Ci::Lint do end end + it_behaves_like 'sets merged yaml' + include_context 'advanced validations' do it 'does not catch advanced logical errors' do expect(subject).to be_valid @@ -203,6 +242,8 @@ RSpec.describe Gitlab::Ci::Lint do end end + it_behaves_like 'sets merged yaml' + include_context 'advanced validations' do it 'runs advanced logical validations' do expect(subject).not_to be_valid diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb index 1f497dea2bf..7da602251a5 100644 --- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb +++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb @@ -4,11 +4,12 @@ require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Parsers::Test::Junit do describe '#parse!' do - subject { described_class.new.parse!(junit, test_suite, args) } + subject { described_class.new.parse!(junit, test_suite, job: job) } let(:test_suite) { Gitlab::Ci::Reports::TestSuite.new('rspec') } let(:test_cases) { flattened_test_cases(test_suite) } - let(:args) { { job: { id: 1, project: "project" } } } + let(:job) { double(max_test_cases_per_report: max_test_cases) } + let(:max_test_cases) { 0 } context 'when data is JUnit style XML' do context 'when there are no <testcases> in <testsuite>' do @@ -43,7 +44,7 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do let(:junit) do <<-EOF.strip_heredoc <testsuites> - <testsuite> + <testsuite name='Math'> <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase> </testsuite> </testsuites> @@ -53,6 +54,7 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do it 'parses XML and adds a test case to a suite' do expect { subject }.not_to raise_error + expect(test_cases[0].suite_name).to eq('Math') expect(test_cases[0].classname).to eq('Calculator') expect(test_cases[0].name).to eq('sumTest1') expect(test_cases[0].execution_time).to eq(0.01) @@ -62,7 +64,7 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do context 'when there is <testcase>' do let(:junit) do <<-EOF.strip_heredoc - <testsuite> + <testsuite name='Math'> <testcase classname='Calculator' name='sumTest1' time='0.01'> #{testcase_content} </testcase> @@ -79,6 +81,7 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do shared_examples_for '<testcase> XML parser' do |status, output| it 'parses XML and adds a test case to the suite' do aggregate_failures do + expect(test_case.suite_name).to eq('Math') expect(test_case.classname).to eq('Calculator') expect(test_case.name).to eq('sumTest1') expect(test_case.execution_time).to eq(0.01) @@ -152,13 +155,15 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do expect { subject }.not_to raise_error expect(test_cases.count).to eq(1) + expect(test_cases.first.suite_name).to eq("XXX\\FrontEnd\\WebBundle\\Tests\\Controller\\LogControllerTest") + expect(test_cases.first.name).to eq("testIndexAction") end end context 'when there are two test cases' do let(:junit) do <<-EOF.strip_heredoc - <testsuite> + <testsuite name='Math'> <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase> <testcase classname='Calculator' name='sumTest2' time='0.02'></testcase> </testsuite> @@ -168,9 +173,11 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do it 'parses XML and adds test cases to a suite' do expect { subject }.not_to raise_error + expect(test_cases[0].suite_name).to eq('Math') expect(test_cases[0].classname).to eq('Calculator') expect(test_cases[0].name).to eq('sumTest1') expect(test_cases[0].execution_time).to eq(0.01) + expect(test_cases[1].suite_name).to eq('Math') expect(test_cases[1].classname).to eq('Calculator') expect(test_cases[1].name).to eq('sumTest2') expect(test_cases[1].execution_time).to eq(0.02) @@ -181,7 +188,7 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do let(:junit) do <<-EOF.strip_heredoc <testsuites> - <testsuite> + <testsuite name='Math'> <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase> <testcase classname='Calculator' name='sumTest2' time='0.02'></testcase> </testsuite> @@ -196,18 +203,81 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do it 'parses XML and adds test cases to a suite' do expect { subject }.not_to raise_error - expect(test_cases[0].classname).to eq('Calculator') - expect(test_cases[0].name).to eq('sumTest1') - expect(test_cases[0].execution_time).to eq(0.01) - expect(test_cases[1].classname).to eq('Calculator') - expect(test_cases[1].name).to eq('sumTest2') - expect(test_cases[1].execution_time).to eq(0.02) - expect(test_cases[2].classname).to eq('Statemachine') - expect(test_cases[2].name).to eq('happy path') - expect(test_cases[2].execution_time).to eq(100) - expect(test_cases[3].classname).to eq('Statemachine') - expect(test_cases[3].name).to eq('unhappy path') - expect(test_cases[3].execution_time).to eq(200) + expect(test_cases).to contain_exactly( + have_attributes( + suite_name: 'Math', + classname: 'Calculator', + name: 'sumTest1', + execution_time: 0.01 + ), + have_attributes( + suite_name: 'Math', + classname: 'Calculator', + name: 'sumTest2', + execution_time: 0.02 + ), + have_attributes( + suite_name: test_suite.name, # Defaults to test suite instance's name + classname: 'Statemachine', + name: 'happy path', + execution_time: 100 + ), + have_attributes( + suite_name: test_suite.name, # Defaults to test suite instance's name + classname: 'Statemachine', + name: 'unhappy path', + execution_time: 200 + ) + ) + end + end + + context 'when number of test cases exceeds the max_test_cases limit' do + let(:max_test_cases) { 1 } + + shared_examples_for 'rejecting too many test cases' do + it 'attaches an error to the TestSuite object' do + expect { subject }.not_to raise_error + expect(test_suite.suite_error).to eq("JUnit data parsing failed: number of test cases exceeded the limit of #{max_test_cases}") + end + end + + context 'and test cases are unique' do + let(:junit) do + <<-EOF.strip_heredoc + <testsuites> + <testsuite> + <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase> + <testcase classname='Calculator' name='sumTest2' time='0.02'></testcase> + </testsuite> + <testsuite> + <testcase classname='Statemachine' name='happy path' time='100'></testcase> + <testcase classname='Statemachine' name='unhappy path' time='200'></testcase> + </testsuite> + </testsuites> + EOF + end + + it_behaves_like 'rejecting too many test cases' + end + + context 'and test cases are duplicates' do + let(:junit) do + <<-EOF.strip_heredoc + <testsuites> + <testsuite> + <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase> + <testcase classname='Calculator' name='sumTest2' time='0.02'></testcase> + </testsuite> + <testsuite> + <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase> + <testcase classname='Calculator' name='sumTest2' time='0.02'></testcase> + </testsuite> + </testsuites> + EOF + end + + it_behaves_like 'rejecting too many test cases' end end end @@ -296,9 +366,7 @@ RSpec.describe Gitlab::Ci::Parsers::Test::Junit do expect(test_cases[0].has_attachment?).to be_truthy expect(test_cases[0].attachment).to eq("some/path.png") - expect(test_cases[0].job).to be_present - expect(test_cases[0].job[:id]).to eq(1) - expect(test_cases[0].job[:project]).to eq("project") + expect(test_cases[0].job).to eq(job) end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb index 74c014b6408..570706bfaac 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build/cache_spec.rb @@ -224,7 +224,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build::Cache do key: 'a-key', paths: ['vendor/ruby'], untracked: true, - policy: 'push' + policy: 'push', + when: 'on_success' } end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 34df0e86a18..0b961336f3f 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do - let(:project) { create(:project, :repository) } - let(:head_sha) { project.repository.head_commit.id } - let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: head_sha) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:head_sha) { project.repository.head_commit.id } + let(:pipeline) { build(:ci_empty_pipeline, project: project, sha: head_sha) } let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage } } let(:previous_stages) { [] } @@ -503,7 +503,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do using RSpec::Parameterized let(:pipeline) do - build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) + build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source, project: project) end context 'matches' do @@ -766,7 +766,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do context 'with a matching changes: rule' do let(:pipeline) do - create(:ci_pipeline, project: project).tap do |pipeline| + build(:ci_pipeline, project: project).tap do |pipeline| stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml]) end end diff --git a/spec/lib/gitlab/ci/reports/test_case_spec.rb b/spec/lib/gitlab/ci/reports/test_case_spec.rb index 7fb208213c1..a142846fc18 100644 --- a/spec/lib/gitlab/ci/reports/test_case_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_case_spec.rb @@ -6,39 +6,26 @@ RSpec.describe Gitlab::Ci::Reports::TestCase do describe '#initialize' do let(:test_case) { described_class.new(params) } - context 'when both classname and name are given' do - context 'when test case is passed' do - let(:job) { build(:ci_build) } - let(:params) { attributes_for(:test_case).merge!(job: job) } - - it 'initializes an instance' do - expect { test_case }.not_to raise_error - - expect(test_case.name).to eq('test-1') - expect(test_case.classname).to eq('trace') - expect(test_case.file).to eq('spec/trace_spec.rb') - expect(test_case.execution_time).to eq(1.23) - expect(test_case.status).to eq(described_class::STATUS_SUCCESS) - expect(test_case.system_output).to be_nil - expect(test_case.job).to be_present - end - end + context 'when required params are given' do + let(:job) { build(:ci_build) } + let(:params) { attributes_for(:test_case).merge!(job: job) } - context 'when test case is failed' do - let(:job) { build(:ci_build) } - let(:params) { attributes_for(:test_case, :failed).merge!(job: job) } - - it 'initializes an instance' do - expect { test_case }.not_to raise_error - - expect(test_case.name).to eq('test-1') - expect(test_case.classname).to eq('trace') - expect(test_case.file).to eq('spec/trace_spec.rb') - expect(test_case.execution_time).to eq(1.23) - expect(test_case.status).to eq(described_class::STATUS_FAILED) - expect(test_case.system_output) - .to eq('Failure/Error: is_expected.to eq(300) expected: 300 got: -100') - end + it 'initializes an instance', :aggregate_failures do + expect { test_case }.not_to raise_error + + expect(test_case).to have_attributes( + suite_name: params[:suite_name], + name: params[:name], + classname: params[:classname], + file: params[:file], + execution_time: params[:execution_time], + status: params[:status], + system_output: params[:system_output], + job: params[:job] + ) + + key = "#{test_case.suite_name}_#{test_case.classname}_#{test_case.name}" + expect(test_case.key).to eq(Digest::SHA256.hexdigest(key)) end end @@ -53,6 +40,10 @@ RSpec.describe Gitlab::Ci::Reports::TestCase do end end + context 'when suite_name is missing' do + it_behaves_like 'param is missing', :suite_name + end + context 'when classname is missing' do it_behaves_like 'param is missing', :classname end diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb index 15fa78444e5..50d1595da73 100644 --- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb @@ -229,6 +229,20 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do end end + describe '#each_test_case' do + before do + test_suite.add_test_case(test_case_success) + test_suite.add_test_case(test_case_failed) + test_suite.add_test_case(test_case_skipped) + test_suite.add_test_case(test_case_error) + end + + it 'yields each test case to given block' do + expect { |b| test_suite.each_test_case(&b) } + .to yield_successive_args(test_case_success, test_case_failed, test_case_skipped, test_case_error) + end + end + Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status_type| describe "##{status_type}_count" do subject { test_suite.public_send("#{status_type}_count") } diff --git a/spec/lib/gitlab/ci/runner/backoff_spec.rb b/spec/lib/gitlab/ci/runner/backoff_spec.rb new file mode 100644 index 00000000000..f147d69f7cd --- /dev/null +++ b/spec/lib/gitlab/ci/runner/backoff_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' +require 'active_support/testing/time_helpers' + +RSpec.describe Gitlab::Ci::Runner::Backoff do + include ActiveSupport::Testing::TimeHelpers + + describe '#duration' do + it 'returns backoff duration from start' do + freeze_time do + described_class.new(5.minutes.ago).then do |backoff| + expect(backoff.duration).to eq 5.minutes + end + end + end + + it 'returns an integer value' do + freeze_time do + described_class.new(5.seconds.ago).then do |backoff| + expect(backoff.duration).to be 5 + end + end + end + + it 'returns the smallest number greater than or equal to duration' do + freeze_time do + described_class.new(0.5.seconds.ago).then do |backoff| + expect(backoff.duration).to be 1 + end + end + end + end + + describe '#slot' do + using RSpec::Parameterized::TableSyntax + + where(:started, :slot) do + 0 | 0 + 0.1 | 0 + 0.9 | 0 + 1 | 0 + 1.1 | 0 + 1.9 | 0 + 2 | 0 + 2.9 | 0 + 3 | 0 + 4 | 1 + 5 | 1 + 6 | 1 + 7 | 1 + 8 | 2 + 9 | 2 + 9.9 | 2 + 10 | 2 + 15 | 2 + 16 | 3 + 31 | 3 + 32 | 4 + 63 | 4 + 64 | 5 + 127 | 5 + 128 | 6 + 250 | 6 + 310 | 7 + 520 | 8 + 999 | 8 + end + + with_them do + it 'falls into an appropaite backoff slot' do + freeze_time do + backoff = described_class.new(started.seconds.ago) + expect(backoff.slot).to eq slot + end + end + end + end + + describe '#to_seconds' do + using RSpec::Parameterized::TableSyntax + + where(:started, :backoff) do + 0 | 1 + 0.1 | 1 + 0.9 | 1 + 1 | 1 + 1.1 | 1 + 1.9 | 1 + 2 | 1 + 3 | 1 + 4 | 2 + 5 | 2 + 6 | 2 + 6.5 | 2 + 7 | 2 + 8 | 4 + 9 | 4 + 9.9 | 4 + 10 | 4 + 15 | 4 + 16 | 8 + 31 | 8 + 32 | 16 + 63 | 16 + 64 | 32 + 127 | 32 + 128 | 64 + 250 | 64 + 310 | 64 + 520 | 64 + 999 | 64 + end + + with_them do + it 'calculates backoff based on an appropriate slot' do + freeze_time do + described_class.new(started.seconds.ago).then do |delay| + expect(delay.to_seconds).to eq backoff + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/bridge/common_spec.rb b/spec/lib/gitlab/ci/status/bridge/common_spec.rb index 92600b21afc..37524afc83d 100644 --- a/spec/lib/gitlab/ci/status/bridge/common_spec.rb +++ b/spec/lib/gitlab/ci/status/bridge/common_spec.rb @@ -30,15 +30,6 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Common do it { expect(subject).to have_details } it { expect(subject.details_path).to include "pipelines/#{downstream_pipeline.id}" } - - context 'when ci_bridge_pipeline_details is disabled' do - before do - stub_feature_flags(ci_bridge_pipeline_details: false) - end - - it { expect(subject).not_to have_details } - it { expect(subject.details_path).to be_nil } - end end context 'when user does not have access to read downstream pipeline' do diff --git a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb index 021b777a0ff..d27bb98ba9a 100644 --- a/spec/lib/gitlab/ci/status/bridge/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/bridge/factory_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory do end context 'when bridge is created' do - let(:bridge) { create(:ci_bridge) } + let(:bridge) { create_bridge(:created) } it 'matches correct core status' do expect(factory.core_status).to be_a Gitlab::Ci::Status::Created @@ -32,7 +32,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory do end context 'when bridge is failed' do - let(:bridge) { create(:ci_bridge, :failed) } + let(:bridge) { create_bridge(:failed) } it 'matches correct core status' do expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed @@ -70,4 +70,61 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Factory do end end end + + context 'when bridge is a manual action' do + let(:bridge) { create_bridge(:playable) } + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Manual + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Bridge::Manual, + Gitlab::Ci::Status::Bridge::Play, + Gitlab::Ci::Status::Bridge::Action] + end + + it 'fabricates action detailed status' do + expect(status).to be_a Gitlab::Ci::Status::Bridge::Action + end + + it 'fabricates status with correct details' do + expect(status.text).to eq s_('CiStatusText|manual') + expect(status.group).to eq 'manual' + expect(status.icon).to eq 'status_manual' + expect(status.favicon).to eq 'favicon_status_manual' + expect(status.illustration).to include(:image, :size, :title, :content) + expect(status.label).to include 'manual play action' + expect(status).not_to have_details + expect(status.action_path).to include 'play' + end + + context 'when user has ability to play action' do + before do + bridge.downstream_project.add_developer(user) + end + + it 'fabricates status that has action' do + expect(status).to have_action + end + end + + context 'when user does not have ability to play action' do + it 'fabricates status that has no action' do + expect(status).not_to have_action + end + end + end + + private + + def create_bridge(trait) + upstream_project = create(:project, :repository) + downstream_project = create(:project, :repository) + upstream_pipeline = create(:ci_pipeline, :running, project: upstream_project) + trigger = { trigger: { project: downstream_project.full_path, branch: 'feature' } } + + create(:ci_bridge, trait, options: trigger, pipeline: upstream_pipeline) + end end diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index a35efae5c57..7fae76f61ea 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Canceled do describe '#group' do it { expect(subject.group).to eq 'canceled' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index 1ddced923f6..1e54d1ed8c5 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Created do describe '#group' do it { expect(subject.group).to eq 'created' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb index e8bd728b740..f3f3304b04d 100644 --- a/spec/lib/gitlab/ci/status/failed_spec.rb +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Failed do describe '#group' do it { expect(subject.group).to eq 'failed' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index 0e47b19d9c1..1c062a0133d 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Pending do describe '#group' do it { expect(subject.group).to eq 'pending' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb index 6d33eb77560..ec1850c1959 100644 --- a/spec/lib/gitlab/ci/status/preparing_spec.rb +++ b/spec/lib/gitlab/ci/status/preparing_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Preparing do describe '#group' do it { expect(subject.group).to eq 'preparing' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb index fbc7bfd81b3..e40d696ee4d 100644 --- a/spec/lib/gitlab/ci/status/running_spec.rb +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Running do describe '#group' do it { expect(subject.group).to eq 'running' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb index 4a1dae937ca..8a923faf3f9 100644 --- a/spec/lib/gitlab/ci/status/scheduled_spec.rb +++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Scheduled do describe '#group' do it { expect(subject.group).to eq 'scheduled' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index f402bbe5221..ac3c2f253f7 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Skipped do describe '#group' do it { expect(subject.group).to eq 'skipped' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb index 2d1c50448d4..f2069334abd 100644 --- a/spec/lib/gitlab/ci/status/success_spec.rb +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::Success do describe '#group' do it { expect(subject.group).to eq 'success' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb index de18198c6c2..bb6139accaf 100644 --- a/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb +++ b/spec/lib/gitlab/ci/status/waiting_for_resource_spec.rb @@ -26,4 +26,8 @@ RSpec.describe Gitlab::Ci::Status::WaitingForResource do describe '#group' do it { expect(subject.group).to eq 'waiting-for-resource' } end + + describe '#details_path' do + it { expect(subject.details_path).to be_nil } + end end diff --git a/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb new file mode 100644 index 00000000000..8df739d9245 --- /dev/null +++ b/spec/lib/gitlab/ci/templates/Terraform/base_gitlab_ci_yaml_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Terraform/Base.latest.gitlab-ci.yml' do + subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform/Base.latest') } + + describe 'the created pipeline' do + let(:user) { create(:admin) } + let(:default_branch) { 'master' } + let(:pipeline_branch) { default_branch } + let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } + let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } + let(:pipeline) { service.execute!(:push) } + let(:build_names) { pipeline.builds.pluck(:name) } + + before do + stub_ci_pipeline_yaml_file(template.content) + allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true) + allow(project).to receive(:default_branch).and_return(default_branch) + end + + it 'does not create any jobs' do + expect(build_names).to be_empty + end + end +end diff --git a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb new file mode 100644 index 00000000000..5eec021b9d7 --- /dev/null +++ b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Terraform.latest.gitlab-ci.yml' do + before do + allow(Gitlab::Template::GitlabCiYmlTemplate).to receive(:excluded_patterns).and_return([]) + end + + subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Terraform.latest') } + + describe 'the created pipeline' do + let_it_be(:user) { create(:admin) } + + let(:default_branch) { 'master' } + let(:pipeline_branch) { default_branch } + let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } + let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) } + let(:pipeline) { service.execute!(:push) } + let(:build_names) { pipeline.builds.pluck(:name) } + + before do + stub_ci_pipeline_yaml_file(template.content) + allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true) + allow(project).to receive(:default_branch).and_return(default_branch) + end + + context 'on master branch' do + it 'creates init, validate and build jobs' do + expect(build_names).to include('init', 'validate', 'build', 'deploy') + end + end + + context 'outside the master branch' do + let(:pipeline_branch) { 'patch-1' } + + before do + project.repository.create_branch(pipeline_branch) + end + + it 'does not creates a deploy and a test job' do + expect(build_names).not_to include('deploy') + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace/checksum_spec.rb b/spec/lib/gitlab/ci/trace/checksum_spec.rb new file mode 100644 index 00000000000..794794c3f69 --- /dev/null +++ b/spec/lib/gitlab/ci/trace/checksum_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Trace::Checksum do + let(:build) { create(:ci_build, :running) } + + subject { described_class.new(build) } + + context 'when build pending state exists' do + before do + create(:ci_build_pending_state, build: build, trace_checksum: 'crc32:d4777540') + end + + context 'when matching persisted trace chunks exist' do + before do + create_chunk(index: 0, data: 'a' * 128.kilobytes) + create_chunk(index: 1, data: 'b' * 128.kilobytes) + create_chunk(index: 2, data: 'ccccccccccccccccc') + end + + it 'calculates combined trace chunks CRC32 correctly' do + expect(subject.chunks_crc32).to eq 3564598592 + expect(subject).to be_valid + end + end + + context 'when trace chunks were persisted in a wrong order' do + before do + create_chunk(index: 0, data: 'b' * 128.kilobytes) + create_chunk(index: 1, data: 'a' * 128.kilobytes) + create_chunk(index: 2, data: 'ccccccccccccccccc') + end + + it 'makes trace checksum invalid' do + expect(subject).not_to be_valid + end + end + + context 'when one of the trace chunks is missing' do + before do + create_chunk(index: 0, data: 'a' * 128.kilobytes) + create_chunk(index: 2, data: 'ccccccccccccccccc') + end + + it 'makes trace checksum invalid' do + expect(subject).not_to be_valid + end + end + + context 'when checksums of persisted trace chunks do not match' do + before do + create_chunk(index: 0, data: 'a' * 128.kilobytes) + create_chunk(index: 1, data: 'X' * 128.kilobytes) + create_chunk(index: 2, data: 'ccccccccccccccccc') + end + + it 'makes trace checksum invalid' do + expect(subject).not_to be_valid + end + end + + context 'when persisted trace chunks are missing' do + it 'makes trace checksum invalid' do + expect(subject.state_crc32).to eq 3564598592 + expect(subject).not_to be_valid + end + end + end + + context 'when build pending state is missing' do + describe '#state_crc32' do + it 'returns nil' do + expect(subject.state_crc32).to be_nil + end + end + + describe '#valid?' do + it { is_expected.not_to be_valid } + end + end + + describe '#trace_chunks' do + before do + create_chunk(index: 0, data: 'abcdefg') + end + + it 'does not load raw_data from a database store' do + subject.trace_chunks.first.then do |chunk| + expect(chunk).to be_database + expect { chunk.raw_data } + .to raise_error ActiveModel::MissingAttributeError + end + end + end + + describe '#last_chunk' do + context 'when there are no chunks' do + it 'returns nil' do + expect(subject.last_chunk).to be_nil + end + end + + context 'when there are multiple chunks' do + before do + create_chunk(index: 1, data: '1234') + create_chunk(index: 0, data: 'abcd') + end + + it 'returns chunk with the highest index' do + expect(subject.last_chunk.chunk_index).to eq 1 + end + end + end + + def create_chunk(index:, data:) + create(:ci_build_trace_chunk, :persisted, build: build, + chunk_index: index, + initial_data: data) + end +end diff --git a/spec/lib/gitlab/ci/trace/metrics_spec.rb b/spec/lib/gitlab/ci/trace/metrics_spec.rb new file mode 100644 index 00000000000..6518d0ab075 --- /dev/null +++ b/spec/lib/gitlab/ci/trace/metrics_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Trace::Metrics, :prometheus do + describe '#increment_trace_bytes' do + context 'when incrementing by more than one' do + it 'increments a single counter' do + subject.increment_trace_bytes(10) + subject.increment_trace_bytes(20) + subject.increment_trace_bytes(30) + + expect(described_class.trace_bytes.get).to eq 60 + expect(described_class.trace_bytes.values.count).to eq 1 + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 171877dbaee..92bf2519588 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do - let(:build) { create(:ci_build) } +RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_default: :keep do + let_it_be(:project) { create_default(:project) } + let_it_be_with_reload(:build) { create(:ci_build) } let(:trace) { described_class.new(build) } describe "associations" do @@ -32,6 +33,16 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do expect(artifact2.job.trace.raw).to eq(test_data) end + + it 'reloads the trace in case of a chunk error' do + chunk_error = described_class::ChunkedIO::FailedToGetChunkError + + allow_any_instance_of(described_class::Stream) + .to receive(:raw).and_raise(chunk_error) + + expect(build).to receive(:reset).and_return(build) + expect { trace.raw }.to raise_error(chunk_error) + end end context 'when live trace feature is disabled' do @@ -111,4 +122,13 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do end end end + + describe '#lock' do + it 'acquires an exclusive lease on the trace' do + trace.lock do + expect { trace.lock } + .to raise_error described_class::LockedError + end + end + end end diff --git a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb new file mode 100644 index 00000000000..7e3cd7ec254 --- /dev/null +++ b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Gitlab + module Ci + class YamlProcessor + RSpec.describe Result do + include StubRequests + + let(:user) { create(:user) } + let(:ci_config) { Gitlab::Ci::Config.new(config_content, user: user) } + let(:result) { described_class.new(ci_config: ci_config, warnings: ci_config&.warnings) } + + describe '#merged_yaml' do + subject(:merged_yaml) { result.merged_yaml } + + let(:config_content) do + YAML.dump( + include: { remote: 'https://example.com/sample.yml' }, + test: { stage: 'test', script: 'echo' } + ) + end + + let(:included_yml) do + YAML.dump( + another_test: { stage: 'test', script: 'echo 2' } + ) + end + + before do + stub_full_request('https://example.com/sample.yml').to_return(body: included_yml) + end + + it 'returns expanded yaml config' do + expanded_config = YAML.safe_load(merged_yaml, [Symbol]) + included_config = YAML.safe_load(included_yml, [Symbol]) + + expect(expanded_config).to include(*included_config.keys) + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index d596494a987..fb6395e888a 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1361,7 +1361,8 @@ module Gitlab paths: ["logs/", "binaries/"], untracked: true, key: 'key', - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1383,7 +1384,8 @@ module Gitlab paths: ["logs/", "binaries/"], untracked: true, key: { files: ['file'] }, - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1402,7 +1404,8 @@ module Gitlab paths: ['logs/', 'binaries/'], untracked: true, key: 'key', - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1425,7 +1428,8 @@ module Gitlab paths: ['logs/', 'binaries/'], untracked: true, key: { files: ['file'] }, - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1448,7 +1452,8 @@ module Gitlab paths: ['logs/', 'binaries/'], untracked: true, key: { files: ['file'], prefix: 'prefix' }, - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end @@ -1468,7 +1473,8 @@ module Gitlab paths: ["test/"], untracked: false, key: 'local', - policy: 'pull-push' + policy: 'pull-push', + when: 'on_success' ) end end @@ -2240,47 +2246,49 @@ module Gitlab end describe 'with parent-child pipeline' do + let(:config) do + YAML.dump({ + build1: { stage: 'build', script: 'test' }, + test1: { + stage: 'test', + trigger: { + include: includes + } + } + }) + end + context 'when artifact and job are specified' do - let(:config) do - YAML.dump({ - build1: { stage: 'build', script: 'test' }, - test1: { stage: 'test', trigger: { - include: [{ artifact: 'generated.yml', job: 'build1' }] - } } - }) - end + let(:includes) { [{ artifact: 'generated.yml', job: 'build1' }] } it { is_expected.to be_valid } end - context 'when job is not specified specified while artifact is' do - let(:config) do - YAML.dump({ - build1: { stage: 'build', script: 'test' }, - test1: { stage: 'test', trigger: { - include: [{ artifact: 'generated.yml' }] - } } - }) - end + context 'when job is not specified while artifact is' do + let(:includes) { [{ artifact: 'generated.yml' }] } it_behaves_like 'returns errors', /include config must specify the job where to fetch the artifact from/ end - context 'when include is a string' do - let(:config) do - YAML.dump({ - build1: { stage: 'build', script: 'test' }, - test1: { - stage: 'test', - trigger: { - include: 'generated.yml' - } - } - }) + context 'when project and file are specified' do + let(:includes) do + [{ file: 'generated.yml', project: 'my-namespace/my-project' }] end it { is_expected.to be_valid } end + + context 'when file is not specified while project is' do + let(:includes) { [{ project: 'something' }] } + + it_behaves_like 'returns errors', /include config must specify the file where to fetch the config from/ + end + + context 'when include is a string' do + let(:includes) { 'generated.yml' } + + it { is_expected.to be_valid } + end end describe "Error handling" do @@ -2457,13 +2465,13 @@ module Gitlab context 'returns errors if variables is not a map' do let(:config) { YAML.dump({ variables: "test", rspec: { script: "test" } }) } - it_behaves_like 'returns errors', 'variables config should be a hash of key value pairs' + it_behaves_like 'returns errors', 'variables config should be a hash of key value pairs, value can be a hash' end context 'returns errors if variables is not a map of key-value strings' do let(:config) { YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) } - it_behaves_like 'returns errors', 'variables config should be a hash of key value pairs' + it_behaves_like 'returns errors', 'variables config should be a hash of key value pairs, value can be a hash' end context 'returns errors if job when is not on_success, on_failure or always' do |