diff options
Diffstat (limited to 'spec/lib/gitlab/ci')
33 files changed, 859 insertions, 207 deletions
diff --git a/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb b/spec/lib/gitlab/ci/build/duration_parser_spec.rb index 889878cf3ef..7f5ff1eb0ee 100644 --- a/spec/lib/gitlab/ci/build/artifacts/expire_in_parser_spec.rb +++ b/spec/lib/gitlab/ci/build/duration_parser_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Build::Artifacts::ExpireInParser do +RSpec.describe Gitlab::Ci::Build::DurationParser do describe '.validate_duration', :request_store do subject { described_class.validate_duration(value) } diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb index 8f77a1f60ad..4895077a731 100644 --- a/spec/lib/gitlab/ci/build/image_spec.rb +++ b/spec/lib/gitlab/ci/build/image_spec.rb @@ -98,9 +98,11 @@ RSpec.describe Gitlab::Ci::Build::Image do let(:service_entrypoint) { '/bin/sh' } let(:service_alias) { 'db' } let(:service_command) { 'sleep 30' } + let(:pull_policy) { %w[always if-not-present] } let(:job) do create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint, - alias: service_alias, command: service_command, ports: [80] }] }) + alias: service_alias, command: service_command, ports: [80], + pull_policy: pull_policy }] }) end it 'fabricates an non-empty array of objects' do @@ -114,6 +116,7 @@ RSpec.describe Gitlab::Ci::Build::Image do expect(subject.first.entrypoint).to eq(service_entrypoint) expect(subject.first.alias).to eq(service_alias) expect(subject.first.command).to eq(service_command) + expect(subject.first.pull_policy).to eq(pull_policy) port = subject.first.ports.first expect(port.number).to eq 80 diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb index 4ac8bf61738..3892b88598a 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb @@ -6,19 +6,43 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do describe '#satisfied_by?' do subject { described_class.new(globs).satisfied_by?(pipeline, context) } - it_behaves_like 'a glob matching rule' do + context 'a glob matching rule' do + using RSpec::Parameterized::TableSyntax + let(:pipeline) { build(:ci_pipeline) } let(:context) {} before do allow(pipeline).to receive(:modified_paths).and_return(files.keys) end + + # rubocop:disable Layout/LineLength + where(:case_name, :globs, :files, :satisfied) do + 'exact top-level match' | ['Dockerfile'] | { 'Dockerfile' => '', 'Gemfile' => '' } | true + 'exact top-level match' | { paths: ['Dockerfile'] } | { 'Dockerfile' => '', 'Gemfile' => '' } | true + 'exact top-level no match' | { paths: ['Dockerfile'] } | { 'Gemfile' => '' } | false + 'pattern top-level match' | { paths: ['Docker*'] } | { 'Dockerfile' => '', 'Gemfile' => '' } | true + 'pattern top-level no match' | ['Docker*'] | { 'Gemfile' => '' } | false + 'pattern top-level no match' | { paths: ['Docker*'] } | { 'Gemfile' => '' } | false + 'exact nested match' | { paths: ['project/build.properties'] } | { 'project/build.properties' => '' } | true + 'exact nested no match' | { paths: ['project/build.properties'] } | { 'project/README.md' => '' } | false + 'pattern nested match' | { paths: ['src/**/*.go'] } | { 'src/gitlab.com/goproject/goproject.go' => '' } | true + 'pattern nested no match' | { paths: ['src/**/*.go'] } | { 'src/gitlab.com/goproject/README.md' => '' } | false + 'ext top-level match' | { paths: ['*.go'] } | { 'main.go' => '', 'cmd/goproject/main.go' => '' } | true + 'ext nested no match' | { paths: ['*.go'] } | { 'cmd/goproject/main.go' => '' } | false + 'ext slash no match' | { paths: ['/*.go'] } | { 'main.go' => '', 'cmd/goproject/main.go' => '' } | false + end + # rubocop:enable Layout/LineLength + + with_them do + it { is_expected.to eq(satisfied) } + end end context 'when pipeline is nil' do let(:pipeline) {} let(:context) {} - let(:globs) { [] } + let(:globs) { { paths: [] } } it { is_expected.to be_truthy } end @@ -26,8 +50,8 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do context 'when using variable expansion' do let(:pipeline) { build(:ci_pipeline) } let(:modified_paths) { ['helm/test.txt'] } - let(:globs) { ['$HELM_DIR/**/*'] } - let(:context) { double('context') } + let(:globs) { { paths: ['$HELM_DIR/**/*'] } } + let(:context) { instance_double(Gitlab::Ci::Build::Context::Base) } before do allow(pipeline).to receive(:modified_paths).and_return(modified_paths) @@ -58,7 +82,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do end context 'when variable expansion does not match' do - let(:globs) { ['path/with/$in/it/*'] } + let(:globs) { { paths: ['path/with/$in/it/*'] } } let(:modified_paths) { ['path/with/$in/it/file.txt'] } before do diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb index f905e229415..ac73b665f3a 100644 --- a/spec/lib/gitlab/ci/build/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb @@ -14,10 +14,14 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do let(:ci_build) { build(:ci_build, pipeline: pipeline) } let(:rule) { described_class.new(rule_hash) } + before do + allow(pipeline).to receive(:modified_paths).and_return(['file.rb']) + end + describe '#matches?' do subject { rule.matches?(pipeline, seed) } - context 'with one matching clause' do + context 'with one matching clause if' do let(:rule_hash) do { if: '$VAR == null', when: 'always' } end @@ -25,9 +29,17 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do it { is_expected.to eq(true) } end + context 'with one matching clause changes' do + let(:rule_hash) do + { changes: { paths: ['**/*'] }, when: 'always' } + end + + it { is_expected.to eq(true) } + end + context 'with two matching clauses' do let(:rule_hash) do - { if: '$VAR == null', changes: '**/*', when: 'always' } + { if: '$VAR == null', changes: { paths: ['**/*'] }, when: 'always' } end it { is_expected.to eq(true) } @@ -35,7 +47,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do context 'with a matching and non-matching clause' do let(:rule_hash) do - { if: '$VAR != null', changes: '$VAR == null', when: 'always' } + { if: '$VAR != null', changes: { paths: ['invalid.xyz'] }, when: 'always' } end it { is_expected.to eq(false) } @@ -43,7 +55,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do context 'with two non-matching clauses' do let(:rule_hash) do - { if: '$VAR != null', changes: 'README', when: 'always' } + { if: '$VAR != null', changes: { paths: ['README'] }, when: 'always' } end it { is_expected.to eq(false) } diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb index bd1ab5d8c41..0fa6d4f8804 100644 --- a/spec/lib/gitlab/ci/config/entry/image_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb @@ -9,6 +9,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do before do stub_feature_flags(ci_docker_image_pull_policy: true) + + entry.compose! end let(:entry) { described_class.new(config) } @@ -129,19 +131,16 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do describe '#valid?' do it 'is valid' do - entry.compose! - expect(entry).to be_valid end context 'when the feature flag ci_docker_image_pull_policy is disabled' do before do stub_feature_flags(ci_docker_image_pull_policy: false) + entry.compose! end it 'is not valid' do - entry.compose! - expect(entry).not_to be_valid expect(entry.errors).to include('image config contains unknown keys: pull_policy') end @@ -150,8 +149,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do describe '#value' do it "returns value" do - entry.compose! - expect(entry.value).to eq( name: 'image:1.0', pull_policy: ['if-not-present'] @@ -161,11 +158,10 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do context 'when the feature flag ci_docker_image_pull_policy is disabled' do before do stub_feature_flags(ci_docker_image_pull_policy: false) + entry.compose! end it 'is not valid' do - entry.compose! - expect(entry.value).to eq( name: 'image:1.0' ) diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb index 3ed4a9f263f..295561b3c4d 100644 --- a/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb @@ -37,7 +37,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do it { is_expected.not_to be_valid } it 'reports an error about invalid policy' do - expect(entry.errors).to include(/should be an array of strings/) + expect(entry.errors).to include(/should be an array or a hash/) end end @@ -64,7 +64,59 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do it 'returns information about errors' do expect(entry.errors) - .to include(/should be an array of strings/) + .to include(/should be an array or a hash/) + end + end + + context 'with paths' do + context 'when paths is an array of strings' do + let(:config) { { paths: %w[app/ lib/] } } + + it { is_expected.to be_valid } + end + + context 'when paths is not an array' do + let(:config) { { paths: 'string' } } + + it { is_expected.not_to be_valid } + + it 'returns information about errors' do + expect(entry.errors) + .to include(/should be an array of strings/) + end + end + + context 'when paths is an array of integers' do + let(:config) { { paths: [1, 2] } } + + it { is_expected.not_to be_valid } + + it 'returns information about errors' do + expect(entry.errors) + .to include(/should be an array of strings/) + end + end + + context 'when paths is an array of long strings' do + let(:config) { { paths: ['a'] * 51 } } + + it { is_expected.not_to be_valid } + + it 'returns information about errors' do + expect(entry.errors) + .to include(/has too many entries \(maximum 50\)/) + end + end + + context 'when paths is nil' do + let(:config) { { paths: nil } } + + it { is_expected.not_to be_valid } + + it 'returns information about errors' do + expect(entry.errors) + .to include(/should be an array of strings/) + end end end end @@ -75,6 +127,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do context 'when using a string array' do let(:config) { %w[app/ lib/ spec/ other/* paths/**/*.rb] } + it { is_expected.to eq(paths: config) } + end + + context 'with paths' do + let(:config) do + { paths: ['app/', 'lib/'] } + end + it { is_expected.to eq(config) } end end 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 89d349efe8f..93f4a66bfb6 100644 --- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb @@ -115,7 +115,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do it { is_expected.not_to be_valid } it 'reports an error about invalid policy' do - expect(subject.errors).to include(/should be an array of strings/) + expect(subject.errors).to include(/should be an array or a hash/) end end @@ -411,7 +411,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do context 'when using a changes: clause' do let(:config) { { changes: %w[app/ lib/ spec/ other/* paths/**/*.rb] } } - it { is_expected.to eq(config) } + it { is_expected.to eq(changes: { paths: %w[app/ lib/ spec/ other/* paths/**/*.rb] }) } + + context 'when using changes with paths' do + let(:config) { { changes: { paths: %w[app/ lib/ spec/ other/* paths/**/*.rb] } } } + + it { is_expected.to eq(config) } + end end context 'when default value has been provided' do @@ -426,7 +432,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do end it 'does not add to provided configuration' do - expect(entry.value).to eq(config) + expect(entry.value).to eq(changes: { paths: %w[app/**/*.rb] }) end end diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb index cfec33003e4..b0871f2345e 100644 --- a/spec/lib/gitlab/ci/config/entry/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'fast_spec_helper' -require 'support/helpers/stub_feature_flags' require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Rules do @@ -12,13 +11,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do end let(:metadata) { { allowed_when: %w[always never] } } - let(:entry) { factory.create! } - describe '.new' do - subject { entry } + subject(:entry) { factory.create! } + describe '.new' do before do - subject.compose! + entry.compose! end context 'with a list of rule rule' do @@ -73,7 +71,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do end describe '#value' do - subject { entry.value } + subject(:value) { entry.value } + + before do + entry.compose! + end context 'with a list of rule rule' do let(:config) do @@ -99,7 +101,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules do { if: '$SKIP', when: 'never' } end - it { is_expected.to eq([config]) } + it { is_expected.to eq([]) } end context 'with nested rules' do diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb index 2795cc9dddf..3c000fd09ed 100644 --- a/spec/lib/gitlab/ci/config/entry/service_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -1,14 +1,19 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' +require 'support/helpers/stubbed_feature' +require 'support/helpers/stub_feature_flags' RSpec.describe Gitlab::Ci::Config::Entry::Service do - let(:entry) { described_class.new(config) } + include StubFeatureFlags before do + stub_feature_flags(ci_docker_image_pull_policy: true) entry.compose! end + subject(:entry) { described_class.new(config) } + context 'when configuration is a string' do let(:config) { 'postgresql:9.5' } @@ -90,6 +95,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do end end + describe '#pull_policy' do + it "returns nil" do + expect(entry.pull_policy).to be_nil + end + end + context 'when configuration has ports' do let(:ports) { [{ number: 80, protocol: 'http', name: 'foobar' }] } let(:config) do @@ -134,6 +145,49 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do end end end + + context 'when configuration has pull_policy' do + let(:config) { { name: 'postgresql:9.5', pull_policy: 'if-not-present' } } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + + context 'when the feature flag ci_docker_image_pull_policy is disabled' do + before do + stub_feature_flags(ci_docker_image_pull_policy: false) + entry.compose! + end + + it 'is not valid' do + expect(entry).not_to be_valid + expect(entry.errors).to include('service config contains unknown keys: pull_policy') + end + end + end + + describe '#value' do + it "returns value" do + expect(entry.value).to eq( + name: 'postgresql:9.5', + pull_policy: ['if-not-present'] + ) + end + + context 'when the feature flag ci_docker_image_pull_policy is disabled' do + before do + stub_feature_flags(ci_docker_image_pull_policy: false) + end + + it 'is not valid' do + expect(entry.value).to eq( + name: 'postgresql:9.5' + ) + end + end + end + end end context 'when entry value is not correct' do diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb index 800c563cd0b..40702e75404 100644 --- a/spec/lib/gitlab/ci/config/external/context_spec.rb +++ b/spec/lib/gitlab/ci/config/external/context_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' RSpec.describe Gitlab::Ci::Config::External::Context do - let(:project) { double('Project') } + let(:project) { build(:project) } let(:user) { double('User') } let(:sha) { '12345' } let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'a', 'value' => 'b' }]) } @@ -126,7 +126,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context do end context 'with attributes' do - let(:new_attributes) { { project: double, user: double, sha: '56789' } } + let(:new_attributes) { { project: build(:project), user: double, sha: '56789' } } it_behaves_like 'a mutated context' end diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb index 77e542cf933..72a85c9b03d 100644 --- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -177,6 +177,22 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do expect(project_file.error_message).to include("Project `xxxxxxxxxxxxxxxxxxxxxxx` not found or access denied!") end end + + context 'when a project contained in an array is used with a masked variable' do + let(:variables) do + Gitlab::Ci::Variables::Collection.new([ + { key: 'VAR1', value: 'a_secret_variable_value', masked: true } + ]) + end + + let(:params) do + { project: ['a_secret_variable_value'], file: '/file.yml' } + end + + it 'does not raise an error' do + expect { valid? }.not_to raise_error + end + end end describe '#expand_context' do diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index 7e1b31fea6a..e74fdc2071b 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -232,11 +232,9 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do image: 'image:1.0' } end - before do - stub_const("#{described_class}::MAX_INCLUDES", 2) - end - it 'does not raise an exception' do + allow(context).to receive(:max_includes).and_return(2) + expect { subject }.not_to raise_error end end @@ -250,11 +248,9 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do image: 'image:1.0' } end - before do - stub_const("#{described_class}::MAX_INCLUDES", 1) - end - it 'raises an exception' do + allow(context).to receive(:max_includes).and_return(1) + expect { subject }.to raise_error(described_class::TooManyIncludesError) end @@ -264,6 +260,8 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do end it 'raises an exception' do + allow(context).to receive(:max_includes).and_return(1) + expect { subject }.to raise_error(described_class::TooManyIncludesError) end end diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 15a0ff40aa4..841a46e197d 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -323,11 +323,9 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do end context 'when too many includes is included' do - before do - stub_const('Gitlab::Ci::Config::External::Mapper::MAX_INCLUDES', 1) - end - it 'raises an error' do + allow(context).to receive(:max_includes).and_return(1) + expect { subject }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError, /Maximum of 1 nested/) end end diff --git a/spec/lib/gitlab/ci/jwt_spec.rb b/spec/lib/gitlab/ci/jwt_spec.rb index 179e2efc0c7..147801b6217 100644 --- a/spec/lib/gitlab/ci/jwt_spec.rb +++ b/spec/lib/gitlab/ci/jwt_spec.rb @@ -48,6 +48,7 @@ RSpec.describe Gitlab::Ci::Jwt do expect(payload[:ref_protected]).to eq(build.protected.to_s) expect(payload[:environment]).to be_nil expect(payload[:environment_protected]).to be_nil + expect(payload[:deployment_tier]).to be_nil end end @@ -96,7 +97,7 @@ RSpec.describe Gitlab::Ci::Jwt do end describe 'environment' do - let(:environment) { build_stubbed(:environment, project: project, name: 'production') } + let(:environment) { build_stubbed(:environment, project: project, name: 'production', tier: 'production') } let(:build) do build_stubbed( :ci_build, @@ -114,6 +115,19 @@ RSpec.describe Gitlab::Ci::Jwt do it 'has correct values for environment attributes' do expect(payload[:environment]).to eq('production') expect(payload[:environment_protected]).to eq('false') + expect(payload[:deployment_tier]).to eq('production') + end + + describe 'deployment_tier' do + context 'when build options specifies a different deployment_tier' do + before do + build.options[:environment] = { name: environment.name, deployment_tier: 'development' } + end + + it 'uses deployment_tier from build options' do + expect(payload[:deployment_tier]).to eq('development') + end + end end end end @@ -121,8 +135,8 @@ RSpec.describe Gitlab::Ci::Jwt do describe '.for_build' do shared_examples 'generating JWT for build' do context 'when signing key is present' do - let(:rsa_key) { OpenSSL::PKey::RSA.generate(1024) } - let(:rsa_key_data) { rsa_key.to_s } + let_it_be(:rsa_key) { OpenSSL::PKey::RSA.generate(3072) } + let_it_be(:rsa_key_data) { rsa_key.to_s } it 'generates JWT with key id' do _payload, headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' }) diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb index 375841ce236..cbf92f8fa83 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } - let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) } + let(:stage) { build(:ci_stage, project: project, statuses: [job]) } let(:pipeline) { create(:ci_pipeline, project: project, stages: [stage]) } let(:command) do diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index 9057c4e99df..eba0db0adfb 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -59,7 +59,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do context 'tags persistence' do let(:stage) do - build(:ci_stage_entity, pipeline: pipeline, project: project) + build(:ci_stage, pipeline: pipeline, project: project) end let(:job) do @@ -77,7 +77,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do context 'without tags' do it 'extracts an empty tag list' do - expect(CommitStatus) + expect(Gitlab::Ci::Tags::BulkInsert) .to receive(:bulk_insert_tags!) .with([job]) .and_call_original @@ -95,7 +95,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do end it 'bulk inserts tags' do - expect(CommitStatus) + expect(Gitlab::Ci::Tags::BulkInsert) .to receive(:bulk_insert_tags!) .with([job]) .and_call_original diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb index 6a7d9b58a05..e07a3ca9033 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do let(:project) { create(:project) } let(:user) { create(:user) } - let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) } + let(:stage) { build(:ci_stage, project: project, statuses: [job]) } let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) } let(:command) do diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb index 571455d6279..f14dd70a753 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureResourceGroups do let(:project) { create(:project) } let(:user) { create(:user) } - let(:stage) { build(:ci_stage_entity, project: project, statuses: [job]) } + let(:stage) { build(:ci_stage, project: project, statuses: [job]) } let(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) } let!(:environment) { create(:environment, name: 'production', project: project) } diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb index cebc4c02d11..eeac0c85a77 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -84,7 +84,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do end end - it 'respects the defined payload schema', :saas do + it 'respects the defined payload schema' do expect(::Gitlab::HTTP).to receive(:post) do |_url, params| expect(params[:body]).to match_schema('/external_validation') expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT) @@ -235,6 +235,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do end it 'logs the authorization' do + allow(Gitlab::AppLogger).to receive(:info) + expect(Gitlab::AppLogger).to receive(:info).with(message: 'Pipeline not authorized', project_id: project.id, user_id: user.id) perform! diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 49505d397c2..040f3ab5830 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -858,14 +858,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do context 'with an explicit `when: never`' do where(:rule_set) do [ - [[{ changes: %w[*/**/*.rb], when: 'never' }, { changes: %w[*/**/*.rb], when: 'always' }]], - [[{ changes: %w[app/models/ci/pipeline.rb], when: 'never' }, { changes: %w[app/models/ci/pipeline.rb], when: 'always' }]], - [[{ changes: %w[spec/**/*.rb], when: 'never' }, { changes: %w[spec/**/*.rb], when: 'always' }]], - [[{ changes: %w[*.yml], when: 'never' }, { changes: %w[*.yml], when: 'always' }]], - [[{ changes: %w[.*.yml], when: 'never' }, { changes: %w[.*.yml], when: 'always' }]], - [[{ changes: %w[**/*], when: 'never' }, { changes: %w[**/*], when: 'always' }]], - [[{ changes: %w[*/**/*.rb *.yml], when: 'never' }, { changes: %w[*/**/*.rb *.yml], when: 'always' }]], - [[{ changes: %w[.*.yml **/*], when: 'never' }, { changes: %w[.*.yml **/*], when: 'always' }]] + [[{ changes: { paths: %w[*/**/*.rb] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb] }, when: 'always' }]], + [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }]], + [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'never' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'always' }]], + [[{ changes: { paths: %w[*.yml] }, when: 'never' }, { changes: { paths: %w[*.yml] }, when: 'always' }]], + [[{ changes: { paths: %w[.*.yml] }, when: 'never' }, { changes: { paths: %w[.*.yml] }, when: 'always' }]], + [[{ changes: { paths: %w[**/*] }, when: 'never' }, { changes: { paths: %w[**/*] }, when: 'always' }]], + [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }]], + [[{ changes: { paths: %w[.*.yml **/*] }, when: 'never' }, { changes: { paths: %w[.*.yml **/*] }, when: 'always' }]] ] end @@ -881,14 +881,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do context 'with an explicit `when: always`' do where(:rule_set) do [ - [[{ changes: %w[*/**/*.rb], when: 'always' }, { changes: %w[*/**/*.rb], when: 'never' }]], - [[{ changes: %w[app/models/ci/pipeline.rb], when: 'always' }, { changes: %w[app/models/ci/pipeline.rb], when: 'never' }]], - [[{ changes: %w[spec/**/*.rb], when: 'always' }, { changes: %w[spec/**/*.rb], when: 'never' }]], - [[{ changes: %w[*.yml], when: 'always' }, { changes: %w[*.yml], when: 'never' }]], - [[{ changes: %w[.*.yml], when: 'always' }, { changes: %w[.*.yml], when: 'never' }]], - [[{ changes: %w[**/*], when: 'always' }, { changes: %w[**/*], when: 'never' }]], - [[{ changes: %w[*/**/*.rb *.yml], when: 'always' }, { changes: %w[*/**/*.rb *.yml], when: 'never' }]], - [[{ changes: %w[.*.yml **/*], when: 'always' }, { changes: %w[.*.yml **/*], when: 'never' }]] + [[{ changes: { paths: %w[*/**/*.rb] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb] }, when: 'never' }]], + [[{ changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'always' }, { changes: { paths: %w[app/models/ci/pipeline.rb] }, when: 'never' }]], + [[{ changes: { paths: %w[spec/**/*.rb] }, when: 'always' }, { changes: { paths: %w[spec/**/*.rb] }, when: 'never' }]], + [[{ changes: { paths: %w[*.yml] }, when: 'always' }, { changes: { paths: %w[*.yml] }, when: 'never' }]], + [[{ changes: { paths: %w[.*.yml] }, when: 'always' }, { changes: { paths: %w[.*.yml] }, when: 'never' }]], + [[{ changes: { paths: %w[**/*] }, when: 'always' }, { changes: { paths: %w[**/*] }, when: 'never' }]], + [[{ changes: { paths: %w[*/**/*.rb *.yml] }, when: 'always' }, { changes: { paths: %w[*/**/*.rb *.yml] }, when: 'never' }]], + [[{ changes: { paths: %w[.*.yml **/*] }, when: 'always' }, { changes: { paths: %w[.*.yml **/*] }, when: 'never' }]] ] end @@ -904,14 +904,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do context 'without an explicit when: value' do where(:rule_set) do [ - [[{ changes: %w[*/**/*.rb] }]], - [[{ changes: %w[app/models/ci/pipeline.rb] }]], - [[{ changes: %w[spec/**/*.rb] }]], - [[{ changes: %w[*.yml] }]], - [[{ changes: %w[.*.yml] }]], - [[{ changes: %w[**/*] }]], - [[{ changes: %w[*/**/*.rb *.yml] }]], - [[{ changes: %w[.*.yml **/*] }]] + [[{ changes: { paths: %w[*/**/*.rb] } }]], + [[{ changes: { paths: %w[app/models/ci/pipeline.rb] } }]], + [[{ changes: { paths: %w[spec/**/*.rb] } }]], + [[{ changes: { paths: %w[*.yml] } }]], + [[{ changes: { paths: %w[.*.yml] } }]], + [[{ changes: { paths: %w[**/*] } }]], + [[{ changes: { paths: %w[*/**/*.rb *.yml] } }]], + [[{ changes: { paths: %w[.*.yml **/*] } }]] ] end diff --git a/spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb b/spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb index eec218346c2..f116b175fc7 100644 --- a/spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb +++ b/spec/lib/gitlab/ci/reports/coverage_report_generator_spec.rb @@ -75,16 +75,6 @@ RSpec.describe Gitlab::Ci::Reports::CoverageReportGenerator, factory_default: :k end it_behaves_like 'having a coverage report' - - context 'when feature flag ci_child_pipeline_coverage_reports is disabled' do - before do - stub_feature_flags(ci_child_pipeline_coverage_reports: false) - end - - it 'returns empty coverage reports' do - expect(subject).to be_empty - end - end end context 'when both parent and child pipeline have builds with coverage reports' do diff --git a/spec/lib/gitlab/ci/reports/test_reports_spec.rb b/spec/lib/gitlab/ci/reports/test_report_spec.rb index 24c00de3731..539510bca9e 100644 --- a/spec/lib/gitlab/ci/reports/test_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_report_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Reports::TestReports do +RSpec.describe Gitlab::Ci::Reports::TestReport do include TestReportsHelper let(:test_reports) { described_class.new } diff --git a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb index 3483dddca3a..ac64e4699fe 100644 --- a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb @@ -6,8 +6,8 @@ RSpec.describe Gitlab::Ci::Reports::TestReportsComparer do include TestReportsHelper let(:comparer) { described_class.new(base_reports, head_reports) } - let(:base_reports) { Gitlab::Ci::Reports::TestReports.new } - let(:head_reports) { Gitlab::Ci::Reports::TestReports.new } + let(:base_reports) { Gitlab::Ci::Reports::TestReport.new } + let(:head_reports) { Gitlab::Ci::Reports::TestReport.new } describe '#suite_comparers' do subject { comparer.suite_comparers } diff --git a/spec/lib/gitlab/ci/runner/metrics_spec.rb b/spec/lib/gitlab/ci/runner/metrics_spec.rb new file mode 100644 index 00000000000..3c459271092 --- /dev/null +++ b/spec/lib/gitlab/ci/runner/metrics_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Runner::Metrics, :prometheus do + subject { described_class.new } + + describe '#increment_runner_authentication_success_counter' do + it 'increments count for same type' do + expect { subject.increment_runner_authentication_success_counter(runner_type: 'instance_type') } + .to change { described_class.runner_authentication_success_counter.get(runner_type: 'instance_type') }.by(1) + end + + it 'does not increment count for different type' do + expect { subject.increment_runner_authentication_success_counter(runner_type: 'group_type') } + .to not_change { described_class.runner_authentication_success_counter.get(runner_type: 'project_type') } + end + + it 'does not increment failure count' do + expect { subject.increment_runner_authentication_success_counter(runner_type: 'project_type') } + .to not_change { described_class.runner_authentication_failure_counter.get } + end + + it 'throws ArgumentError for invalid runner type' do + expect { subject.increment_runner_authentication_success_counter(runner_type: 'unknown_type') } + .to raise_error(ArgumentError, 'unknown runner type: unknown_type') + end + end + + describe '#increment_runner_authentication_failure_counter' do + it 'increments count' do + expect { subject.increment_runner_authentication_failure_counter } + .to change { described_class.runner_authentication_failure_counter.get }.by(1) + end + + it 'does not increment success count' do + expect { subject.increment_runner_authentication_failure_counter } + .to not_change { described_class.runner_authentication_success_counter.get(runner_type: 'instance_type') } + end + end +end diff --git a/spec/lib/gitlab/ci/runner_releases_spec.rb b/spec/lib/gitlab/ci/runner_releases_spec.rb index 9e4a8739c0f..576eb02ad83 100644 --- a/spec/lib/gitlab/ci/runner_releases_spec.rb +++ b/spec/lib/gitlab/ci/runner_releases_spec.rb @@ -5,16 +5,25 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::RunnerReleases do subject { described_class.instance } - describe '#releases' do - before do - subject.reset! + let(:runner_releases_url) { 'the release API URL' } - stub_application_setting(public_runner_releases_url: 'the release API URL') - allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response(response) } - end + def releases + subject.releases + end + + def releases_by_minor + subject.releases_by_minor + end + + before do + subject.reset_backoff! - def releases - subject.releases + stub_application_setting(public_runner_releases_url: runner_releases_url) + end + + describe 'caching behavior', :use_clean_rails_memory_store_caching do + before do + allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything).once { mock_http_response(response) } end shared_examples 'requests that follow cache status' do |validity_period| @@ -25,9 +34,14 @@ RSpec.describe Gitlab::Ci::RunnerReleases do releases travel followup_request_interval do - expect(Gitlab::HTTP).not_to receive(:try_get) + expect(Gitlab::HTTP).not_to receive(:get) - expect(releases).to eq(expected_result) + if expected_releases + expected_result_by_minor = expected_releases.group_by(&:without_patch).transform_values(&:max) + end + + expect(releases).to eq(expected_releases) + expect(releases_by_minor).to eq(expected_result_by_minor) end end end @@ -40,75 +54,189 @@ RSpec.describe Gitlab::Ci::RunnerReleases do releases travel followup_request_interval do - expect(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response(followup_response) } - - expect(releases).to eq((expected_result || []) + [Gitlab::VersionInfo.new(14, 9, 2)]) + expect(Gitlab::HTTP).to receive(:get) + .with(runner_releases_url, anything) + .once { mock_http_response(followup_response) } + + new_releases = (expected_releases || []) + [Gitlab::VersionInfo.new(14, 9, 2)] + new_releases_by_minor_version = (expected_releases_by_minor || {}).merge( + Gitlab::VersionInfo.new(14, 9, 0) => Gitlab::VersionInfo.new(14, 9, 2) + ) + expect(releases).to eq(new_releases) + expect(releases_by_minor).to eq(new_releases_by_minor_version) end end end end - context 'when response is nil' do - let(:response) { nil } - let(:expected_result) { nil } - - it 'returns nil' do - expect(releases).to be_nil - end - - it_behaves_like 'requests that follow cache status', 5.seconds - + shared_examples 'a service implementing exponential backoff' do |opts| it 'performs exponential backoff on requests', :aggregate_failures do start_time = Time.now.utc.change(usec: 0) http_call_timestamp_offsets = [] - allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL') do + allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything) do http_call_timestamp_offsets << Time.now.utc - start_time + + raise Net::OpenTimeout if opts&.dig(:raise_timeout) + mock_http_response(response) end # An initial HTTP request fails travel_to(start_time) - subject.reset! + subject.reset_backoff! expect(releases).to be_nil + expect(releases_by_minor).to be_nil # Successive failed requests result in HTTP requests only after specific backoff periods backoff_periods = [5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 3600].map(&:seconds) backoff_periods.each do |period| travel(period - 1.second) expect(releases).to be_nil + expect(releases_by_minor).to be_nil travel 1.second expect(releases).to be_nil + expect(releases_by_minor).to be_nil end expect(http_call_timestamp_offsets).to eq([0, 5, 15, 35, 75, 155, 315, 635, 1275, 2555, 5115, 8715]) # Finally a successful HTTP request results in releases being returned - allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response([{ 'name' => 'v14.9.1' }]) } + allow(Gitlab::HTTP).to receive(:get) + .with(runner_releases_url, anything) + .once { mock_http_response([{ 'name' => 'v14.9.1-beta1-ee' }]) } travel 1.hour expect(releases).not_to be_nil + expect(releases_by_minor).not_to be_nil end end + context 'when request results in timeout' do + let(:response) { } + let(:expected_releases) { nil } + let(:expected_releases_by_minor) { nil } + + it_behaves_like 'requests that follow cache status', 5.seconds + it_behaves_like 'a service implementing exponential backoff', raise_timeout: true + end + + context 'when response is nil' do + let(:response) { nil } + let(:expected_releases) { nil } + let(:expected_releases_by_minor) { nil } + + it_behaves_like 'requests that follow cache status', 5.seconds + it_behaves_like 'a service implementing exponential backoff' + end + context 'when response is not nil' do - let(:response) { [{ 'name' => 'v14.9.1' }, { 'name' => 'v14.9.0' }] } - let(:expected_result) { [Gitlab::VersionInfo.new(14, 9, 0), Gitlab::VersionInfo.new(14, 9, 1)] } + let(:response) { [{ 'name' => 'v14.9.1-beta1-ee' }, { 'name' => 'v14.9.0' }] } + let(:expected_releases) do + [ + Gitlab::VersionInfo.new(14, 9, 0), + Gitlab::VersionInfo.new(14, 9, 1, '-beta1-ee') + ] + end + + let(:expected_releases_by_minor) do + { + Gitlab::VersionInfo.new(14, 9, 0) => Gitlab::VersionInfo.new(14, 9, 1, '-beta1-ee') + } + end + + it_behaves_like 'requests that follow cache status', 1.day + end + end + + describe '#releases', :use_clean_rails_memory_store_caching do + before do + allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything).once { mock_http_response(response) } + end + + context 'when response is nil' do + let(:response) { nil } + let(:expected_result) { nil } + + it 'returns nil' do + expect(releases).to be_nil + end + end + + context 'when response is not nil' do + let(:response) { [{ 'name' => 'v14.9.1-beta1-ee' }, { 'name' => 'v14.9.0' }] } + let(:expected_result) do + [ + Gitlab::VersionInfo.new(14, 9, 0), + Gitlab::VersionInfo.new(14, 9, 1, '-beta1-ee') + ] + end it 'returns parsed and sorted Gitlab::VersionInfo objects' do expect(releases).to eq(expected_result) end + end - it_behaves_like 'requests that follow cache status', 1.day + context 'when response contains unexpected input type' do + let(:response) { 'error' } + + it { expect(releases).to be_nil } + end + + context 'when response contains unexpected input array' do + let(:response) { ['error'] } + + it { expect(releases).to be_nil } + end + end + + describe '#releases_by_minor', :use_clean_rails_memory_store_caching do + before do + allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything).once { mock_http_response(response) } end - def mock_http_response(response) - http_response = instance_double(HTTParty::Response) + context 'when response is nil' do + let(:response) { nil } + let(:expected_result) { nil } - allow(http_response).to receive(:success?).and_return(response.present?) - allow(http_response).to receive(:parsed_response).and_return(response) + it 'returns nil' do + expect(releases_by_minor).to be_nil + end + end - http_response + context 'when response is not nil' do + let(:response) { [{ 'name' => 'v14.9.1-beta1-ee' }, { 'name' => 'v14.9.0' }, { 'name' => 'v14.8.1' }] } + let(:expected_result) do + { + Gitlab::VersionInfo.new(14, 8, 0) => Gitlab::VersionInfo.new(14, 8, 1), + Gitlab::VersionInfo.new(14, 9, 0) => Gitlab::VersionInfo.new(14, 9, 1, '-beta1-ee') + } + end + + it 'returns parsed and grouped Gitlab::VersionInfo objects' do + expect(releases_by_minor).to eq(expected_result) + end end + + context 'when response contains unexpected input type' do + let(:response) { 'error' } + + it { expect(releases_by_minor).to be_nil } + end + + context 'when response contains unexpected input array' do + let(:response) { ['error'] } + + it { expect(releases_by_minor).to be_nil } + end + end + + def mock_http_response(response) + http_response = instance_double(HTTParty::Response) + + allow(http_response).to receive(:success?).and_return(!response.nil?) + allow(http_response).to receive(:parsed_response).and_return(response) + + http_response end end diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb index 0353432741b..f2507a24b10 100644 --- a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb +++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb @@ -3,84 +3,156 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do - include StubVersion using RSpec::Parameterized::TableSyntax describe '#check_runner_upgrade_status' do subject(:result) { described_class.instance.check_runner_upgrade_status(runner_version) } + let(:gitlab_version) { '14.1.1' } + let(:parsed_runner_version) { ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true) } + before do - runner_releases_double = instance_double(Gitlab::Ci::RunnerReleases) + allow(described_class.instance).to receive(:gitlab_version) + .and_return(::Gitlab::VersionInfo.parse(gitlab_version)) + end + + context 'with failing Gitlab::Ci::RunnerReleases request' do + let(:runner_version) { '14.1.123' } + let(:runner_releases_double) { instance_double(Gitlab::Ci::RunnerReleases) } + + before do + allow(Gitlab::Ci::RunnerReleases).to receive(:instance).and_return(runner_releases_double) + allow(runner_releases_double).to receive(:releases).and_return(nil) + end - allow(Gitlab::Ci::RunnerReleases).to receive(:instance).and_return(runner_releases_double) - allow(runner_releases_double).to receive(:releases).and_return(available_runner_releases.map { |v| ::Gitlab::VersionInfo.parse(v) }) + it 'returns :error' do + is_expected.to eq({ error: parsed_runner_version }) + end end - context 'with available_runner_releases configured up to 14.1.1' do - let(:available_runner_releases) { %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2 14.1.0 14.1.1 14.1.1-rc3] } + context 'with available_runner_releases configured' do + before do + url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url - context 'with nil runner_version' do - let(:runner_version) { nil } + WebMock.stub_request(:get, url).to_return( + body: available_runner_releases.map { |v| { name: v } }.to_json, + status: 200, + headers: { 'Content-Type' => 'application/json' } + ) + end - it 'returns :invalid' do - is_expected.to eq(:invalid) + context 'with no available runner releases' do + let(:available_runner_releases) do + %w[] end - end - context 'with invalid runner_version' do - let(:runner_version) { 'junk' } + context 'with Gitlab::VERSION set to 14.1.1' do + let(:gitlab_version) { '14.1.1' } - it 'raises ArgumentError' do - expect { subject }.to raise_error(ArgumentError) + context 'with runner_version from last minor release' do + let(:runner_version) { 'v14.0.1' } + + it 'returns :not_available' do + is_expected.to eq({ not_available: parsed_runner_version }) + end + end end end - context 'with Gitlab::VERSION set to 14.1.123' do - before do - stub_version('14.1.123', 'deadbeef') + context 'up to 14.1.1' do + let(:available_runner_releases) do + %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2-rc1 14.0.2 14.1.0 14.1.1] + end + + context 'with nil runner_version' do + let(:runner_version) { nil } - described_class.instance.reset! + it 'returns :invalid_version' do + is_expected.to match({ invalid_version: anything }) + end end - context 'with a runner_version that is too recent' do - let(:runner_version) { 'v14.2.0' } + context 'with invalid runner_version' do + let(:runner_version) { 'junk' } - it 'returns :not_available' do - is_expected.to eq(:not_available) + it 'returns :invalid_version' do + is_expected.to match({ invalid_version: anything }) end end - end - context 'with Gitlab::VERSION set to 14.0.1' do - before do - stub_version('14.0.1', 'deadbeef') + context 'with Gitlab::VERSION set to 14.1.123' do + let(:gitlab_version) { '14.1.123' } + + context 'with a runner_version that is too recent' do + let(:runner_version) { 'v14.2.0' } - described_class.instance.reset! + it 'returns :not_available' do + is_expected.to eq({ not_available: parsed_runner_version }) + end + end + end + + context 'with Gitlab::VERSION set to 14.0.1' do + let(:gitlab_version) { '14.0.1' } + + context 'with valid params' do + where(:runner_version, :expected_result, :expected_suggested_version) do + 'v15.0.0' | :not_available | '15.0.0' # not available since the GitLab instance is still on 14.x, a major version might be incompatible, and a patch upgrade is not available + 'v14.1.0-rc3' | :recommended | '14.1.1' # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes + 'v14.1.0~beta.1574.gf6ea9389' | :recommended | '14.1.1' # suffixes are correctly handled + 'v14.1.0/1.1.0' | :recommended | '14.1.1' # suffixes are correctly handled + 'v14.1.0' | :recommended | '14.1.1' # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes + 'v14.0.1' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available + 'v14.0.2-rc1' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available and we'll move out of a release candidate + 'v14.0.2' | :not_available | '14.0.2' # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version + 'v13.10.1' | :available | '14.0.2' # available upgrade: 14.0.2 + 'v13.10.1~beta.1574.gf6ea9389' | :recommended | '13.10.1' # suffixes are correctly handled, official 13.10.1 is available + 'v13.10.1/1.1.0' | :recommended | '13.10.1' # suffixes are correctly handled, official 13.10.1 is available + 'v13.10.0' | :recommended | '13.10.1' # recommended upgrade since 13.10.1 is available + 'v13.9.2' | :recommended | '14.0.2' # recommended upgrade since backports are no longer released for this version + 'v13.9.0' | :recommended | '14.0.2' # recommended upgrade since backports are no longer released for this version + 'v13.8.1' | :recommended | '14.0.2' # recommended upgrade since build is too old (missing in records) + 'v11.4.1' | :recommended | '14.0.2' # recommended upgrade since build is too old (missing in records) + end + + with_them do + it { is_expected.to eq({ expected_result => Gitlab::VersionInfo.parse(expected_suggested_version) }) } + end + end end - context 'with valid params' do - where(:runner_version, :expected_result) do - 'v15.0.0' | :not_available # not available since the GitLab instance is still on 14.x and a major version might be incompatible - 'v14.1.0-rc3' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes - 'v14.1.0~beta.1574.gf6ea9389' | :recommended # suffixes are correctly handled - 'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled - 'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes - 'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available - 'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version - 'v13.10.1' | :available # available upgrade: 14.1.1 - 'v13.10.1~beta.1574.gf6ea9389' | :available # suffixes are correctly handled - 'v13.10.1/1.1.0' | :available # suffixes are correctly handled - 'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available - 'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version - 'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version - 'v13.8.1' | :recommended # recommended upgrade since build is too old (missing in records) - 'v11.4.1' | :recommended # recommended upgrade since build is too old (missing in records) + context 'with Gitlab::VERSION set to 13.9.0' do + let(:gitlab_version) { '13.9.0' } + + context 'with valid params' do + where(:runner_version, :expected_result, :expected_suggested_version) do + 'v14.0.0' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available, even though the GitLab instance is still on 13.x and a major version might be incompatible + 'v13.10.1' | :not_available | '13.10.1' # not available since 13.10.1 is already ahead of GitLab instance version and is the latest patch update for 13.10.x + 'v13.10.0' | :recommended | '13.10.1' # recommended upgrade since 13.10.1 is available + 'v13.9.2' | :not_available | '13.9.2' # not_available even though backports are no longer released for this version because the runner is already on the same version as the GitLab version + 'v13.9.0' | :recommended | '13.9.2' # recommended upgrade since backports are no longer released for this version + 'v13.8.1' | :recommended | '13.9.2' # recommended upgrade since build is too old (missing in records) + 'v11.4.1' | :recommended | '13.9.2' # recommended upgrade since build is too old (missing in records) + end + + with_them do + it { is_expected.to eq({ expected_result => Gitlab::VersionInfo.parse(expected_suggested_version) }) } + end end + end + end + + context 'up to 15.1.0' do + let(:available_runner_releases) { %w[14.9.1 14.9.2 14.10.0 14.10.1 15.0.0 15.1.0] } + + context 'with Gitlab::VERSION set to 15.2.0-pre' do + let(:gitlab_version) { '15.2.0-pre' } + + context 'with unknown runner version' do + let(:runner_version) { '14.11.0~beta.29.gd0c550e3' } - with_them do - it 'returns symbol representing expected upgrade status' do - is_expected.to be_a(Symbol) - is_expected.to eq(expected_result) + it 'recommends 15.1.0 since 14.11 is an unknown release and 15.1.0 is available' do + is_expected.to eq({ recommended: Gitlab::VersionInfo.new(15, 1, 0) }) end end end diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index e0f5531f370..35d44281072 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -7,9 +7,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } - let(:stage) do - build(:ci_stage, pipeline: pipeline, name: 'test') - end + let(:stage) { create(:ci_stage, pipeline: pipeline) } subject do described_class.new(stage, user) @@ -26,11 +24,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do context 'when stage has a core status' do (Ci::HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status| context "when core status is #{core_status}" do - before do - create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status) - create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status) - create(:ci_build, pipeline: pipeline, stage: 'build', status: :failed) - end + let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) } it "fabricates a core status #{core_status}" do expect(status).to be_a( @@ -48,12 +42,12 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do context 'when stage has warnings' do let(:stage) do - build(:ci_stage, name: 'test', status: :success, pipeline: pipeline) + create(:ci_stage, status: :success, pipeline: pipeline) end before do create(:ci_build, :allowed_to_fail, :failed, - stage: 'test', pipeline: stage.pipeline) + stage_id: stage.id, pipeline: stage.pipeline) end it 'fabricates extended "success with warnings" status' do @@ -70,11 +64,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory do context 'when stage has manual builds' do (Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status| context "when status is #{core_status}" do - before do - create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status) - create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status) - create(:ci_build, pipeline: pipeline, stage: 'build', status: :manual) - end + let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) } it 'fabricates a play manual status' do expect(status).to be_a(Gitlab::Ci::Status::Stage::PlayManual) diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb index 25b79ff2099..9fdaddc083e 100644 --- a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do end describe '#action_path' do - let(:stage) { create(:ci_stage_entity, status: 'manual') } + let(:stage) { create(:ci_stage, status: 'manual') } let(:pipeline) { stage.pipeline } let(:play_manual) { stage.detailed_status(create(:user)) } @@ -46,25 +46,25 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do subject { described_class.matches?(stage, user) } context 'when stage is skipped' do - let(:stage) { create(:ci_stage_entity, status: :skipped) } + let(:stage) { create(:ci_stage, status: :skipped) } it { is_expected.to be_truthy } end context 'when stage is manual' do - let(:stage) { create(:ci_stage_entity, status: :manual) } + let(:stage) { create(:ci_stage, status: :manual) } it { is_expected.to be_truthy } end context 'when stage is scheduled' do - let(:stage) { create(:ci_stage_entity, status: :scheduled) } + let(:stage) { create(:ci_stage, status: :scheduled) } it { is_expected.to be_truthy } end context 'when stage is success' do - let(:stage) { create(:ci_stage_entity, status: :success) } + let(:stage) { create(:ci_stage, status: :success) } context 'and does not have manual builds' do it { is_expected.to be_falsy } diff --git a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb index 6c4f69fb036..5ab859241c6 100644 --- a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb +++ b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do let(:error_message) do <<~MESSAGE A mechanism depending on internals of 'act-as-taggable-on` has been designed - to bulk insert tags for Ci::Build records. + to bulk insert tags for Ci::Build/Ci::Runner records. Please review the code carefully before updating the gem version https://gitlab.com/gitlab-org/gitlab/-/issues/350053 MESSAGE @@ -27,6 +27,21 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do it { expect(ActsAsTaggableOn::VERSION).to eq(acceptable_version), error_message } end + describe '.bulk_insert_tags!' do + let(:inserter) { instance_double(described_class) } + + it 'delegates to bulk insert class' do + expect(Gitlab::Ci::Tags::BulkInsert) + .to receive(:new) + .with(statuses) + .and_return(inserter) + + expect(inserter).to receive(:insert!) + + described_class.bulk_insert_tags!(statuses) + end + end + describe '#insert!' do context 'without tags' do it { expect(service.insert!).to be_falsey } @@ -44,6 +59,50 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do expect(job.reload.tag_list).to match_array(%w[tag1 tag2]) expect(other_job.reload.tag_list).to match_array(%w[tag2 tag3 tag4]) end + + it 'persists taggings' do + service.insert! + + expect(job.taggings.size).to eq(2) + expect(other_job.taggings.size).to eq(3) + + expect(Ci::Build.tagged_with('tag1')).to include(job) + expect(Ci::Build.tagged_with('tag2')).to include(job, other_job) + expect(Ci::Build.tagged_with('tag3')).to include(other_job) + end + + it 'strips tags' do + job.tag_list = [' taga', 'tagb ', ' tagc '] + + service.insert! + expect(job.tags.map(&:name)).to match_array(%w[taga tagb tagc]) + end + + context 'when batching inserts for tags' do + before do + stub_const("#{described_class}::TAGS_BATCH_SIZE", 2) + end + + it 'inserts tags in batches' do + recorder = ActiveRecord::QueryRecorder.new { service.insert! } + count = recorder.log.count { |query| query.include?('INSERT INTO "tags"') } + + expect(count).to eq(2) + end + end + + context 'when batching inserts for taggings' do + before do + stub_const("#{described_class}::TAGGINGS_BATCH_SIZE", 2) + end + + it 'inserts taggings in batches' do + recorder = ActiveRecord::QueryRecorder.new { service.insert! } + count = recorder.log.count { |query| query.include?('INSERT INTO "taggings"') } + + expect(count).to eq(3) + end + end end context 'with tags for only one job' do @@ -57,6 +116,15 @@ RSpec.describe Gitlab::Ci::Tags::BulkInsert do expect(job.reload.tag_list).to match_array(%w[tag1 tag2]) expect(other_job.reload.tag_list).to be_empty end + + it 'persists taggings' do + service.insert! + + expect(job.taggings.size).to eq(2) + + expect(Ci::Build.tagged_with('tag1')).to include(job) + expect(Ci::Build.tagged_with('tag2')).to include(job) + end end end end diff --git a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb index 27de8324206..65fd2b016ac 100644 --- a/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb +++ b/spec/lib/gitlab/ci/templates/AWS/deploy_ecs_gitlab_ci_yaml_spec.rb @@ -34,6 +34,16 @@ RSpec.describe 'Deploy-ECS.gitlab-ci.yml' do expect(build_names).to include('production_ecs') end + context 'when the DAST template is also included' do + let(:dast_template) { Gitlab::Template::GitlabCiYmlTemplate.find('Security/DAST') } + + before do + stub_ci_pipeline_yaml_file(template.content + dast_template.content) + end + + include_examples 'no pipeline yaml error' + end + context 'when running a pipeline for a branch' do let(:pipeline_branch) { 'test_branch' } diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb index b0704ad7f50..8ec0846bdca 100644 --- a/spec/lib/gitlab/ci/variables/builder_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder_spec.rb @@ -166,9 +166,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder do allow(builder).to receive(:secret_instance_variables) { [var('J', 10), var('K', 10)] } allow(builder).to receive(:secret_group_variables) { [var('K', 11), var('L', 11)] } allow(builder).to receive(:secret_project_variables) { [var('L', 12), var('M', 12)] } - allow(job).to receive(:trigger_request) { double(user_variables: [var('M', 13), var('N', 13)]) } - allow(pipeline).to receive(:variables) { [var('N', 14), var('O', 14)] } - allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('O', 15), var('P', 15)]) } + allow(pipeline).to receive(:variables) { [var('M', 13), var('N', 13)] } + allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('N', 14), var('O', 14)]) } end it 'returns variables in order depending on resource hierarchy' do @@ -185,8 +184,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder do var('K', 11), var('L', 11), var('L', 12), var('M', 12), var('M', 13), var('N', 13), - var('N', 14), var('O', 14), - var('O', 15), var('P', 15)]) + var('N', 14), var('O', 14)]) end it 'overrides duplicate keys depending on resource hierarchy' do @@ -198,7 +196,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder do 'I' => '9', 'J' => '10', 'K' => '11', 'L' => '12', 'M' => '13', 'N' => '14', - 'O' => '15', 'P' => '15') + 'O' => '14') end end diff --git a/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb b/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb new file mode 100644 index 00000000000..0bd9563d191 --- /dev/null +++ b/spec/lib/gitlab/ci/yaml_processor/feature_flags_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Gitlab::Ci::YamlProcessor::FeatureFlags do + let(:feature_flag) { :my_feature_flag } + + context 'when the actor is set' do + let(:actor) { double } + let(:another_actor) { double } + + it 'checks the feature flag using the given actor' do + described_class.with_actor(actor) do + expect(Feature).to receive(:enabled?).with(feature_flag, actor) + + described_class.enabled?(feature_flag) + end + end + + it 'returns the value of the block' do + result = described_class.with_actor(actor) do + :test + end + + expect(result).to eq(:test) + end + + it 'restores the existing actor if any' do + described_class.with_actor(actor) do + described_class.with_actor(another_actor) do + expect(Feature).to receive(:enabled?).with(feature_flag, another_actor) + + described_class.enabled?(feature_flag) + end + + expect(Feature).to receive(:enabled?).with(feature_flag, actor) + described_class.enabled?(feature_flag) + end + end + + it 'restores the actor to nil after the block' do + described_class.with_actor(actor) do + expect(Thread.current[described_class::ACTOR_KEY]).to eq(actor) + end + + expect(Thread.current[described_class::ACTOR_KEY]).to be nil + end + end + + context 'when feature flag is checked outside the "with_actor" block' do + it 'raises an error on dev/test environment' do + expect { described_class.enabled?(feature_flag) }.to raise_error(described_class::NoActorError) + end + + context 'when on production' do + before do + allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false) + end + + it 'checks the feature flag without actor' do + expect(Feature).to receive(:enabled?).with(feature_flag, nil) + expect(Gitlab::ErrorTracking) + .to receive(:track_and_raise_for_dev_exception) + .and_call_original + + described_class.enabled?(feature_flag) + end + end + end + + context 'when actor is explicitly nil' do + it 'checks the feature flag without actor' do + described_class.with_actor(nil) do + expect(Feature).to receive(:enabled?).with(feature_flag, nil) + + described_class.enabled?(feature_flag) + 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 3dd9ca35881..22bc6b0db59 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -70,7 +70,7 @@ module Gitlab options: { script: ['rspec'] }, rules: [ { if: '$CI_COMMIT_REF_NAME == "master"' }, - { changes: %w[README.md] } + { changes: { paths: %w[README.md] } } ], allow_failure: false, when: 'on_success', @@ -980,7 +980,7 @@ module Gitlab it { is_expected.to be_valid } - it "returns image and service when defined" do + it "returns with image" do expect(processor.stage_builds_attributes("test")).to contain_exactly({ stage: "test", stage_idx: 2, @@ -1010,6 +1010,51 @@ module Gitlab end end end + + context 'when a service has pull_policy' do + let(:config) do + <<~YAML + services: + - name: postgres:11.9 + pull_policy: if-not-present + + test: + script: exit 0 + YAML + end + + it { is_expected.to be_valid } + + it "returns with service" do + expect(processor.stage_builds_attributes("test")).to contain_exactly({ + stage: "test", + stage_idx: 2, + name: "test", + only: { refs: %w[branches tags] }, + options: { + script: ["exit 0"], + services: [{ name: "postgres:11.9", pull_policy: ["if-not-present"] }] + }, + allow_failure: false, + when: "on_success", + job_variables: [], + root_variables_inheritance: true, + scheduling_type: :stage + }) + end + + context 'when the feature flag ci_docker_image_pull_policy is disabled' do + before do + stub_feature_flags(ci_docker_image_pull_policy: false) + end + + it { is_expected.not_to be_valid } + + it "returns no job" do + expect(processor.jobs).to eq({}) + end + end + end end describe 'Variables' do @@ -2848,6 +2893,51 @@ module Gitlab end end + describe 'Rules' do + context 'changes' do + let(:config) do + <<~YAML + rspec: + script: exit 0 + rules: + - changes: [README.md] + YAML + end + + it 'returns builds with correct rules' do + expect(processor.builds.size).to eq(1) + expect(processor.builds[0]).to match( + hash_including( + name: "rspec", + rules: [{ changes: { paths: ["README.md"] } }] + ) + ) + end + + context 'with paths' do + let(:config) do + <<~YAML + rspec: + script: exit 0 + rules: + - changes: + paths: [README.md] + YAML + end + + it 'returns builds with correct rules' do + expect(processor.builds.size).to eq(1) + expect(processor.builds[0]).to match( + hash_including( + name: "rspec", + rules: [{ changes: { paths: ["README.md"] } }] + ) + ) + end + end + end + end + describe '#execute' do subject { Gitlab::Ci::YamlProcessor.new(content).execute } |