diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 13:37:47 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 13:37:47 +0000 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/lib/gitlab/ci | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) | |
download | gitlab-ce-aee0a117a889461ce8ced6fcf73207fe017f1d99.tar.gz |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/lib/gitlab/ci')
27 files changed, 727 insertions, 130 deletions
diff --git a/spec/lib/gitlab/ci/build/context/build_spec.rb b/spec/lib/gitlab/ci/build/context/build_spec.rb index 46447231424..7f862a3b80a 100644 --- a/spec/lib/gitlab/ci/build/context/build_spec.rb +++ b/spec/lib/gitlab/ci/build/context/build_spec.rb @@ -8,11 +8,7 @@ RSpec.describe Gitlab::Ci::Build::Context::Build do let(:context) { described_class.new(pipeline, seed_attributes) } - describe '#variables' do - subject { context.variables.to_hash } - - it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } - + shared_examples 'variables collection' do it { is_expected.to include('CI_COMMIT_REF_NAME' => 'master') } it { is_expected.to include('CI_PIPELINE_IID' => pipeline.iid.to_s) } it { is_expected.to include('CI_PROJECT_PATH' => pipeline.project.full_path) } @@ -27,4 +23,20 @@ RSpec.describe Gitlab::Ci::Build::Context::Build do it { is_expected.to include('CI_PROJECT_PATH' => pipeline.project.full_path) } end end + + describe '#variables' do + subject { context.variables.to_hash } + + it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } + + it_behaves_like 'variables collection' + end + + describe '#variables_hash' do + subject { context.variables_hash } + + it { expect(context.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } + + it_behaves_like 'variables collection' + end end diff --git a/spec/lib/gitlab/ci/build/context/global_spec.rb b/spec/lib/gitlab/ci/build/context/global_spec.rb index 61f2b90426d..d4141eb8389 100644 --- a/spec/lib/gitlab/ci/build/context/global_spec.rb +++ b/spec/lib/gitlab/ci/build/context/global_spec.rb @@ -8,11 +8,7 @@ RSpec.describe Gitlab::Ci::Build::Context::Global do let(:context) { described_class.new(pipeline, yaml_variables: yaml_variables) } - describe '#variables' do - subject { context.variables.to_hash } - - it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } - + shared_examples 'variables collection' do it { is_expected.to include('CI_COMMIT_REF_NAME' => 'master') } it { is_expected.to include('CI_PIPELINE_IID' => pipeline.iid.to_s) } it { is_expected.to include('CI_PROJECT_PATH' => pipeline.project.full_path) } @@ -26,4 +22,20 @@ RSpec.describe Gitlab::Ci::Build::Context::Global do it { is_expected.to include('SUPPORTED' => 'parsed') } end end + + describe '#variables' do + subject { context.variables.to_hash } + + it { expect(context.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } + + it_behaves_like 'variables collection' + end + + describe '#variables_hash' do + subject { context.variables_hash } + + it { is_expected.to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } + + it_behaves_like 'variables collection' + end end diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb index 6c8c968dc0c..436ad59bdf7 100644 --- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Gitlab::Ci::Build::Policy::Variables do let(:seed) do double('build seed', to_resource: ci_build, - variables: ci_build.scoped_variables + variables_hash: ci_build.scoped_variables.to_hash ) end @@ -91,7 +91,7 @@ RSpec.describe Gitlab::Ci::Build::Policy::Variables do let(:seed) do double('bridge seed', to_resource: bridge, - variables: ci_build.scoped_variables + variables_hash: ci_build.scoped_variables.to_hash ) end 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 d20ea6c9202..532c83f6768 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 @@ -33,12 +33,12 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do end context 'when context has the specified variables' do - let(:variables) do - [{ key: "HELM_DIR", value: "helm", public: true }] + let(:variables_hash) do + { 'HELM_DIR' => 'helm' } end before do - allow(context).to receive(:variables).and_return(variables) + allow(context).to receive(:variables_hash).and_return(variables_hash) end it { is_expected.to be_truthy } @@ -49,7 +49,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do let(:modified_paths) { ['path/with/$in/it/file.txt'] } before do - allow(context).to receive(:variables).and_return([]) + allow(context).to receive(:variables_hash).and_return({}) end it { is_expected.to be_truthy } diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb index 6f3c9278677..f905e229415 100644 --- a/spec/lib/gitlab/ci/build/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule do let(:seed) do double('build seed', to_resource: ci_build, - variables: ci_build.scoped_variables + variables_hash: ci_build.scoped_variables.to_hash ) end diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb index 1d5bdf30278..37bfdca4d1d 100644 --- a/spec/lib/gitlab/ci/build/rules_spec.rb +++ b/spec/lib/gitlab/ci/build/rules_spec.rb @@ -3,13 +3,13 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Build::Rules do - let(:pipeline) { create(:ci_pipeline) } - let(:ci_build) { build(:ci_build, pipeline: pipeline) } + let_it_be(:pipeline) { create(:ci_pipeline) } + let_it_be(:ci_build) { build(:ci_build, pipeline: pipeline) } let(:seed) do double('build seed', to_resource: ci_build, - variables: ci_build.scoped_variables + variables_hash: ci_build.scoped_variables.to_hash ) end diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb index 6c9c8fa5df5..62feed3dda0 100644 --- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb @@ -163,7 +163,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do }) end - it { is_expected.not_to be_valid } + it { is_expected.to be_valid } end context 'when bridge configuration uses rules with only' do diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 0bb26babfc0..885f3eaff79 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -118,6 +118,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do end end + context 'when config uses both "when:" and "rules:"' do + let(:config) do + { + script: 'echo', + when: 'on_failure', + rules: [{ if: '$VARIABLE', when: 'on_success' }] + } + end + + it 'is valid' do + expect(entry).to be_valid + end + end + context 'when delayed job' do context 'when start_in is specified' do let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } } @@ -268,21 +282,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do end end - context 'when it uses both "when:" and "rules:"' do - let(:config) do - { - script: 'echo', - when: 'on_failure', - rules: [{ if: '$VARIABLE', when: 'on_success' }] - } - end - - it 'returns an error about when: being combined with rules' do - expect(entry).not_to be_valid - expect(entry.errors).to include 'job config key may not be used with `rules`: when' - end - end - context 'when delayed job' do context 'when start_in is specified' do let(:config) { { script: 'echo', when: 'delayed', start_in: '1 week' } } diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb index c9c28e2eb8b..5b9337ede34 100644 --- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb @@ -33,6 +33,20 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do end end + context 'when config uses both "when:" and "rules:"' do + let(:config) do + { + script: 'echo', + when: 'on_failure', + rules: [{ if: '$VARIABLE', when: 'on_success' }] + } + end + + it 'is valid' do + expect(entry).to be_valid + end + end + context 'when job name is more than 255' do let(:entry) { node_class.new(config, name: ('a' * 256).to_sym) } @@ -90,21 +104,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do end end - context 'when it uses both "when:" and "rules:"' do - let(:config) do - { - script: 'echo', - when: 'on_failure', - rules: [{ if: '$VARIABLE', when: 'on_success' }] - } - end - - it 'returns an error about when: being combined with rules' do - expect(entry).not_to be_valid - expect(entry.errors).to include 'job config key may not be used with `rules`: when' - end - end - context 'when only: is used with rules:' do let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } } diff --git a/spec/lib/gitlab/ci/config/entry/tags_spec.rb b/spec/lib/gitlab/ci/config/entry/tags_spec.rb index 79317de373b..e05d4ae52b2 100644 --- a/spec/lib/gitlab/ci/config/entry/tags_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/tags_spec.rb @@ -36,25 +36,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Tags do context 'when tags limit is reached' do let(:config) { Array.new(50) {|i| "tag-#{i}" } } - context 'when ci_build_tags_limit is enabled' do - before do - stub_feature_flags(ci_build_tags_limit: true) - end - - it 'reports error' do - expect(entry.errors) - .to include "tags config must be less than the limit of #{described_class::TAGS_LIMIT} tags" - end - end - - context 'when ci_build_tags_limit is disabled' do - before do - stub_feature_flags(ci_build_tags_limit: false) - end - - it 'does not report an error' do - expect(entry.errors).to be_empty - end + it 'reports error' do + expect(entry.errors) + .to include "tags config must be less than the limit of #{described_class::TAGS_LIMIT} tags" end end end diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb index 4b9adf7e87b..800c563cd0b 100644 --- a/spec/lib/gitlab/ci/config/external/context_spec.rb +++ b/spec/lib/gitlab/ci/config/external/context_spec.rb @@ -6,7 +6,8 @@ RSpec.describe Gitlab::Ci::Config::External::Context do let(:project) { double('Project') } let(:user) { double('User') } let(:sha) { '12345' } - let(:attributes) { { project: project, user: user, sha: sha } } + let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'a', 'value' => 'b' }]) } + let(:attributes) { { project: project, user: user, sha: sha, variables: variables } } subject(:subject) { described_class.new(**attributes) } @@ -15,6 +16,9 @@ RSpec.describe Gitlab::Ci::Config::External::Context do it { is_expected.to have_attributes(**attributes) } it { expect(subject.expandset).to eq(Set.new) } it { expect(subject.execution_deadline).to eq(0) } + it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } + it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } + it { expect(subject.variables_hash).to include('a' => 'b') } end context 'without values' do @@ -23,6 +27,8 @@ RSpec.describe Gitlab::Ci::Config::External::Context do it { is_expected.to have_attributes(**attributes) } it { expect(subject.expandset).to eq(Set.new) } it { expect(subject.execution_deadline).to eq(0) } + it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } + it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } end end @@ -94,6 +100,15 @@ RSpec.describe Gitlab::Ci::Config::External::Context do end describe '#mutate' do + let(:attributes) do + { + project: project, + user: user, + sha: sha, + logger: double('logger') + } + end + shared_examples 'a mutated context' do let(:mutated) { subject.mutate(new_attributes) } @@ -107,6 +122,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context do it { expect(mutated).to have_attributes(new_attributes) } it { expect(mutated.expandset).to eq(subject.expandset) } it { expect(mutated.execution_deadline).to eq(mutated.execution_deadline) } + it { expect(mutated.logger).to eq(mutated.logger) } end context 'with attributes' do diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 2e9e6f95071..97bd74721f2 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do include StubRequests let_it_be(:project) { create(:project, :repository) } - let_it_be(:another_project) { create(:project, :repository) } + let_it_be_with_reload(:another_project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let(:sha) { '12345' } @@ -251,6 +251,17 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do it 'properly expands all includes' do is_expected.to include(:my_build, :remote_build, :rspec) end + + it 'propagates the pipeline logger' do + processor.perform + + process_obs_count = processor + .logger + .observations_hash + .dig('config_mapper_process_duration_s', 'count') + + expect(process_obs_count).to eq(3) + end end context 'when user is reporter of another project' do diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb index 1e42cb30ae7..091bd3b07e6 100644 --- a/spec/lib/gitlab/ci/config/external/rules_spec.rb +++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do subject(:rules) { described_class.new(rule_hashes) } describe '#evaluate' do - let(:context) { double(variables: {}) } + let(:context) { double(variables_hash: {}) } subject(:result) { rules.evaluate(context).pass? } @@ -20,13 +20,13 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do let(:rule_hashes) { [{ if: '$MY_VAR == "hello"' }] } context 'when the rule matches' do - let(:context) { double(variables: { MY_VAR: 'hello' }) } + let(:context) { double(variables_hash: { 'MY_VAR' => 'hello' }) } it { is_expected.to eq(true) } end context 'when the rule does not match' do - let(:context) { double(variables: { MY_VAR: 'invalid' }) } + let(:context) { double(variables_hash: { 'MY_VAR' => 'invalid' }) } it { is_expected.to eq(false) } end diff --git a/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb b/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb index f487fccdab7..60b4e01f382 100644 --- a/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb +++ b/spec/lib/gitlab/ci/parsers/terraform/tfplan_spec.rb @@ -103,7 +103,7 @@ RSpec.describe Gitlab::Ci::Parsers::Terraform::Tfplan do 'create' => 0, 'update' => 1, 'delete' => 0, - 'job_name' => artifact.job.options.dig(:artifacts, :name).to_s + 'job_name' => artifact.job.name ) ) ) @@ -124,7 +124,7 @@ RSpec.describe Gitlab::Ci::Parsers::Terraform::Tfplan do 'create' => 0, 'update' => 1, 'delete' => 0, - 'job_name' => artifact.job.options.dig(:artifacts, :name).to_s + 'job_name' => artifact.job.name ) ) ) diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb new file mode 100644 index 00000000000..28bc685286f --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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(:pipeline) { create(:ci_pipeline, project: project, stages: [stage]) } + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) + end + + let(:step) { described_class.new(pipeline, command) } + + describe '#perform!' do + subject { step.perform! } + + before do + job.pipeline = pipeline + end + + context 'when a pipeline contains a deployment job' do + let!(:job) { build(:ci_build, :start_review_app, project: project) } + let!(:environment) { create(:environment, project: project, name: job.expanded_environment_name) } + + it 'creates a deployment record' do + expect { subject }.to change { Deployment.count }.by(1) + + job.reset + expect(job.deployment.project).to eq(job.project) + expect(job.deployment.ref).to eq(job.ref) + expect(job.deployment.sha).to eq(job.sha) + expect(job.deployment.deployable).to eq(job) + expect(job.deployment.deployable_type).to eq('CommitStatus') + expect(job.deployment.environment).to eq(job.persisted_environment) + end + + context 'when creation failure occures' do + before do + allow_next_instance_of(Deployment) do |deployment| + allow(deployment).to receive(:save!) { raise ActiveRecord::RecordInvalid } + end + end + + it 'trackes the exception' do + expect { subject }.to raise_error(described_class::DeploymentCreationError) + + expect(Deployment.count).to eq(0) + end + end + + context 'when the corresponding environment does not exist' do + let!(:environment) { } + + it 'does not create a deployment record' do + expect { subject }.not_to change { Deployment.count } + + expect(job.deployment).to be_nil + end + end + + context 'when create_deployment_in_separate_transaction feature flag is disabled' do + before do + stub_feature_flags(create_deployment_in_separate_transaction: false) + end + + it 'does not create a deployment record' do + expect { subject }.not_to change { Deployment.count } + + expect(job.deployment).to be_nil + end + end + end + + context 'when a pipeline contains a teardown job' do + let!(:job) { build(:ci_build, :stop_review_app, project: project) } + let!(:environment) { create(:environment, name: job.expanded_environment_name) } + + it 'does not create a deployment record' do + expect { subject }.not_to change { Deployment.count } + + expect(job.deployment).to be_nil + end + end + + context 'when a pipeline does not contain a deployment job' do + let!(:job) { build(:ci_build, project: project) } + + it 'does not create any deployments' do + expect { subject }.not_to change { Deployment.count } + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index d60ecc80a6e..4206483b228 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -56,4 +56,74 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Create do .to include /Failed to persist the pipeline/ end end + + context 'tags persistence' do + let(:stage) do + build(:ci_stage_entity, pipeline: pipeline) + end + + let(:job) do + build(:ci_build, stage: stage, pipeline: pipeline, project: project) + end + + let(:bridge) do + build(:ci_bridge, stage: stage, pipeline: pipeline, project: project) + end + + before do + pipeline.stages = [stage] + stage.statuses = [job, bridge] + end + + context 'without tags' do + it 'extracts an empty tag list' do + expect(CommitStatus) + .to receive(:bulk_insert_tags!) + .with(stage.statuses, {}) + .and_call_original + + step.perform! + + expect(job.instance_variable_defined?(:@tag_list)).to be_falsey + expect(job).to be_persisted + expect(job.tag_list).to eq([]) + end + end + + context 'with tags' do + before do + job.tag_list = %w[tag1 tag2] + end + + it 'bulk inserts tags' do + expect(CommitStatus) + .to receive(:bulk_insert_tags!) + .with(stage.statuses, { job.name => %w[tag1 tag2] }) + .and_call_original + + step.perform! + + expect(job.instance_variable_defined?(:@tag_list)).to be_falsey + expect(job).to be_persisted + expect(job.tag_list).to match_array(%w[tag1 tag2]) + end + end + + context 'when the feature flag is disabled' do + before do + job.tag_list = %w[tag1 tag2] + stub_feature_flags(ci_bulk_insert_tags: false) + end + + it 'follows the old code path' do + expect(CommitStatus).not_to receive(:bulk_insert_tags!) + + step.perform! + + expect(job.instance_variable_defined?(:@tag_list)).to be_truthy + expect(job).to be_persisted + expect(job.reload.tag_list).to match_array(%w[tag1 tag2]) + end + end + end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb new file mode 100644 index 00000000000..253928e1a19 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +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(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) } + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) + end + + let(:step) { described_class.new(pipeline, command) } + + describe '#perform!' do + subject { step.perform! } + + before do + job.pipeline = pipeline + end + + context 'when a pipeline contains a deployment job' do + let!(:job) { build(:ci_build, :start_review_app, project: project) } + + it 'ensures environment existence for the job' do + expect { subject }.to change { Environment.count }.by(1) + + expect(project.environments.find_by_name('review/master')).to be_present + expect(job.persisted_environment.name).to eq('review/master') + expect(job.metadata.expanded_environment_name).to eq('review/master') + end + + context 'when an environment has already been existed' do + before do + create(:environment, project: project, name: 'review/master') + end + + it 'ensures environment existence for the job' do + expect { subject }.not_to change { Environment.count } + + expect(project.environments.find_by_name('review/master')).to be_present + expect(job.persisted_environment.name).to eq('review/master') + expect(job.metadata.expanded_environment_name).to eq('review/master') + end + end + + context 'when an environment name contains an invalid character' do + let(:pipeline) { build(:ci_pipeline, ref: '!!!', project: project, stages: [stage]) } + + it 'sets the failure status' do + expect { subject }.not_to change { Environment.count } + + expect(job).to be_failed + expect(job).to be_environment_creation_failure + expect(job.persisted_environment).to be_nil + end + end + + context 'when create_deployment_in_separate_transaction feature flag is disabled' do + before do + stub_feature_flags(create_deployment_in_separate_transaction: false) + end + + it 'does not create any environments' do + expect { subject }.not_to change { Environment.count } + + expect(job.persisted_environment).to be_nil + end + end + end + + context 'when a pipeline contains a teardown job' do + let!(:job) { build(:ci_build, :stop_review_app, project: project) } + + it 'ensures environment existence for the job' do + expect { subject }.to change { Environment.count }.by(1) + + expect(project.environments.find_by_name('review/master')).to be_present + expect(job.persisted_environment.name).to eq('review/master') + expect(job.metadata.expanded_environment_name).to eq('review/master') + end + end + + context 'when a pipeline does not contain a deployment job' do + let!(:job) { build(:ci_build, project: project) } + + it 'does not create any environments' do + expect { subject }.not_to change { Environment.count } + end + end + end +end 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 new file mode 100644 index 00000000000..87df5a3e21b --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +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(:pipeline) { build(:ci_pipeline, project: project, stages: [stage]) } + let!(:environment) { create(:environment, name: 'production', project: project) } + + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) + end + + let(:step) { described_class.new(pipeline, command) } + + describe '#perform!' do + subject { step.perform! } + + before do + job.pipeline = pipeline + end + + context 'when a pipeline contains a job that requires a resource group' do + let!(:job) do + build(:ci_build, project: project, environment: 'production', options: { resource_group_key: '$CI_ENVIRONMENT_NAME' }) + end + + it 'ensures the resource group existence' do + expect { subject }.to change { Ci::ResourceGroup.count }.by(1) + + expect(project.resource_groups.find_by_key('production')).to be_present + expect(job.resource_group.key).to eq('production') + expect(job.options[:resource_group_key]).to be_nil + end + + context 'when a resource group has already been existed' do + before do + create(:ci_resource_group, project: project, key: 'production') + end + + it 'ensures the resource group existence' do + expect { subject }.not_to change { Ci::ResourceGroup.count } + + expect(project.resource_groups.find_by_key('production')).to be_present + expect(job.resource_group.key).to eq('production') + expect(job.options[:resource_group_key]).to be_nil + end + end + + context 'when a resource group key contains an invalid character' do + let!(:job) do + build(:ci_build, project: project, environment: '!!!', options: { resource_group_key: '$CI_ENVIRONMENT_NAME' }) + end + + it 'does not create any resource groups' do + expect { subject }.not_to change { Ci::ResourceGroup.count } + + expect(job.resource_group).to be_nil + end + end + + context 'when create_deployment_in_separate_transaction feature flag is disabled' do + before do + stub_feature_flags(create_deployment_in_separate_transaction: false) + end + + it 'does not create any resource groups' do + expect { subject }.not_to change { Ci::ResourceGroup.count } + + expect(job.resource_group).to be_nil + end + end + end + + context 'when a pipeline does not contain a job that requires a resource group' do + let!(:job) { build(:ci_build, project: project) } + + it 'does not create any resource groups' do + expect { subject }.not_to change { Ci::ResourceGroup.count } + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb index cf21c98dbd5..cebc4c02d11 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -24,6 +24,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do second_stage_job_name: stage: second_stage services: + - - postgres before_script: - echo 'first hello' @@ -142,6 +143,23 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do perform! end + + it 'returns expected payload' do + expect(::Gitlab::HTTP).to receive(:post) do |_url, params| + payload = Gitlab::Json.parse(params[:body]) + + builds = payload['builds'] + expect(builds.count).to eq(2) + expect(builds[0]['services']).to be_nil + expect(builds[0]['stage']).to eq('first_stage') + expect(builds[0]['image']).to eq('hello_world') + expect(builds[1]['services']).to eq(['postgres']) + expect(builds[1]['stage']).to eq('second_stage') + expect(builds[1]['image']).to be_nil + end + + perform! + end end context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb index 115674edc48..3e10ca686ba 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb @@ -17,30 +17,33 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do end describe '#evaluate' do - it 'returns variable value if it is defined' do - variable = described_class.new('VARIABLE') + let(:lexeme) { described_class.new('VARIABLE') } - expect(variable.evaluate(VARIABLE: 'my variable')) + it 'returns variable value if it is defined' do + expect(lexeme.evaluate(VARIABLE: 'my variable')) .to eq 'my variable' end it 'allows to use a string as a variable key too' do - variable = described_class.new('VARIABLE') - - expect(variable.evaluate('VARIABLE' => 'my variable')) + expect(lexeme.evaluate('VARIABLE' => 'my variable')) .to eq 'my variable' end it 'returns nil if it is not defined' do - variable = described_class.new('VARIABLE') - - expect(variable.evaluate(OTHER: 'variable')).to be_nil + expect(lexeme.evaluate('OTHER' => 'variable')).to be_nil + expect(lexeme.evaluate(OTHER: 'variable')).to be_nil end it 'returns an empty string if it is empty' do - variable = described_class.new('VARIABLE') + expect(lexeme.evaluate('VARIABLE' => '')).to eq '' + expect(lexeme.evaluate(VARIABLE: '')).to eq '' + end + + it 'does not call with_indifferent_access unnecessarily' do + variables_hash = { VARIABLE: 'my variable' }.with_indifferent_access - expect(variable.evaluate(VARIABLE: '')).to eq '' + expect(variables_hash).not_to receive(:with_indifferent_access) + expect(lexeme.evaluate(variables_hash)).to eq 'my variable' end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index ec7eebdc056..84713e2a798 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do .append(key: 'PATH_VARIABLE', value: 'a/path/variable/value') .append(key: 'FULL_PATH_VARIABLE', value: '/a/full/path/variable/value') .append(key: 'EMPTY_VARIABLE', value: '') + .to_hash end subject do diff --git a/spec/lib/gitlab/ci/pipeline/logger_spec.rb b/spec/lib/gitlab/ci/pipeline/logger_spec.rb new file mode 100644 index 00000000000..0b44e35dec1 --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/logger_spec.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Gitlab::Ci::Pipeline::Logger do + let_it_be(:project) { build_stubbed(:project) } + let_it_be(:pipeline) { build_stubbed(:ci_pipeline, project: project) } + + subject(:logger) { described_class.new(project: project) } + + describe '#log_when' do + it 'stores blocks for later evaluation' do + logger.log_when { |obs| true } + + expect(logger.send(:log_conditions).first).to be_a(Proc) + end + end + + describe '#instrument' do + it "returns the block's value" do + expect(logger.instrument(:expensive_operation) { 123 }).to eq(123) + end + + it 'records durations of instrumented operations' do + loggable_data = { + 'expensive_operation_duration_s' => { + 'count' => 1, + 'avg' => a_kind_of(Numeric), + 'max' => a_kind_of(Numeric), + 'min' => a_kind_of(Numeric) + } + } + + logger.instrument(:expensive_operation) { 123 } + expect(logger.observations_hash).to match(a_hash_including(loggable_data)) + end + + it 'raises an error when block is not provided' do + expect { logger.instrument(:expensive_operation) } + .to raise_error(ArgumentError, 'block not given') + end + end + + describe '#observe' do + it 'records durations of observed operations' do + loggable_data = { + 'pipeline_creation_duration_s' => { + 'avg' => 30, 'count' => 1, 'max' => 30, 'min' => 30 + } + } + + expect(logger.observe(:pipeline_creation_duration_s, 30)).to be_truthy + expect(logger.observations_hash).to match(a_hash_including(loggable_data)) + end + end + + describe '#commit' do + subject(:commit) { logger.commit(pipeline: pipeline, caller: 'source') } + + before do + stub_feature_flags(ci_pipeline_creation_logger: flag) + allow(logger).to receive(:current_monotonic_time) { Time.current.to_i } + + logger.instrument(:pipeline_save) { travel(60.seconds) } + logger.observe(:pipeline_creation_duration_s, 30) + logger.observe(:pipeline_creation_duration_s, 10) + end + + context 'when the feature flag is enabled' do + let(:flag) { true } + + let(:loggable_data) do + { + 'class' => described_class.name.to_s, + 'pipeline_id' => pipeline.id, + 'pipeline_persisted' => true, + 'project_id' => project.id, + 'pipeline_creation_service_duration_s' => a_kind_of(Numeric), + 'pipeline_creation_caller' => 'source', + 'pipeline_source' => pipeline.source, + 'pipeline_save_duration_s' => { + 'avg' => 60, 'count' => 1, 'max' => 60, 'min' => 60 + }, + 'pipeline_creation_duration_s' => { + 'avg' => 20, 'count' => 2, 'max' => 30, 'min' => 10 + } + } + end + + it 'logs to application.json' do + expect(Gitlab::AppJsonLogger) + .to receive(:info) + .with(a_hash_including(loggable_data)) + .and_call_original + + expect(commit).to be_truthy + end + + context 'with log conditions' do + it 'does not log when the conditions are false' do + logger.log_when { |_obs| false } + + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + expect(commit).to be_falsey + end + + it 'logs when a condition is true' do + logger.log_when { |_obs| true } + logger.log_when { |_obs| false } + + expect(Gitlab::AppJsonLogger) + .to receive(:info) + .with(a_hash_including(loggable_data)) + .and_call_original + + expect(commit).to be_truthy + end + end + end + + context 'when the feature flag is disabled' do + let(:flag) { false } + + it 'does not log' do + expect(Gitlab::AppJsonLogger).not_to receive(:info) + + expect(commit).to be_falsey + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index e2b64e65938..68806fbf287 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do let(:pipeline) { build(:ci_empty_pipeline, project: project, sha: head_sha) } let(:root_variables) { [] } let(:seed_context) { double(pipeline: pipeline, root_variables: root_variables) } - let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage } } + let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage, when: 'on_success' } } let(:previous_stages) { [] } let(:current_stage) { double(seeds_names: [attributes[:name]]) } @@ -61,17 +61,35 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do end end - context 'with job:rules but no explicit when:' do - context 'is matched' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null' }] } } + context 'with job: rules but no explicit when:' do + let(:base_attributes) { { name: 'rspec', ref: 'master' } } + + context 'with a manual job' do + context 'with a matched rule' do + let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR == null' }]) } + + it { is_expected.to include(when: 'manual') } + end - it { is_expected.to include(when: 'on_success') } + context 'is not matched' do + let(:attributes) { base_attributes.merge(when: 'manual', rules: [{ if: '$VAR != null' }]) } + + it { is_expected.to include(when: 'never') } + end end - context 'is not matched' do - let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null' }] } } + context 'with an automatic job' do + context 'is matched' do + let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR == null' }]) } - it { is_expected.to include(when: 'never') } + it { is_expected.to include(when: 'on_success') } + end + + context 'is not matched' do + let(:attributes) { base_attributes.merge(when: 'on_success', rules: [{ if: '$VAR != null' }]) } + + it { is_expected.to include(when: 'never') } + end end end @@ -393,6 +411,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do describe '#to_resource' do subject { seed_build.to_resource } + before do + stub_feature_flags(create_deployment_in_separate_transaction: false) + end + context 'when job is Ci::Build' do it { is_expected.to be_a(::Ci::Build) } it { is_expected.to be_valid } @@ -443,6 +465,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it_behaves_like 'deployment job' it_behaves_like 'ensures environment existence' + context 'when create_deployment_in_separate_transaction feature flag is enabled' do + before do + stub_feature_flags(create_deployment_in_separate_transaction: true) + end + + it 'does not create any deployments nor environments' do + expect(subject.deployment).to be_nil + expect(Environment.count).to eq(0) + expect(Deployment.count).to eq(0) + end + end + context 'when the environment name is invalid' do let(:attributes) { { name: 'deploy', ref: 'master', environment: '!!!' } } @@ -452,25 +486,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do expect(subject.metadata.expanded_environment_name).to be_nil expect(Environment.exists?(name: expected_environment_name)).to eq(false) end - - context 'when surface_environment_creation_failure feature flag is disabled' do - before do - stub_feature_flags(surface_environment_creation_failure: false) - end - - it_behaves_like 'non-deployment job' - it_behaves_like 'ensures environment inexistence' - - it 'tracks an exception' do - expect(Gitlab::ErrorTracking).to receive(:track_exception) - .with(an_instance_of(described_class::EnvironmentCreationFailure), - project_id: project.id, - reason: %q{Name can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces, but it cannot start or end with '/'}) - .once - - subject - end - end end end @@ -515,6 +530,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it 'returns a job with resource group' do expect(subject.resource_group).not_to be_nil expect(subject.resource_group.key).to eq('iOS') + expect(Ci::ResourceGroup.count).to eq(1) + end + + context 'when create_deployment_in_separate_transaction feature flag is enabled' do + before do + stub_feature_flags(create_deployment_in_separate_transaction: true) + end + + it 'does not create any resource groups' do + expect(subject.resource_group).to be_nil + expect(Ci::ResourceGroup.count).to eq(0) + end end context 'when resource group has $CI_ENVIRONMENT_NAME in it' do @@ -892,7 +919,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do context 'using rules:' do using RSpec::Parameterized - let(:attributes) { { name: 'rspec', rules: rule_set } } + let(:attributes) { { name: 'rspec', rules: rule_set, when: 'on_success' } } context 'with a matching if: rule' do context 'with an explicit `when: never`' do diff --git a/spec/lib/gitlab/ci/status/bridge/common_spec.rb b/spec/lib/gitlab/ci/status/bridge/common_spec.rb index 37524afc83d..30e6ad234a0 100644 --- a/spec/lib/gitlab/ci/status/bridge/common_spec.rb +++ b/spec/lib/gitlab/ci/status/bridge/common_spec.rb @@ -29,7 +29,15 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Common do end it { expect(subject).to have_details } - it { expect(subject.details_path).to include "pipelines/#{downstream_pipeline.id}" } + it { expect(subject.details_path).to include "jobs/#{bridge.id}" } + + context 'with ci_retry_downstream_pipeline ff disabled' do + before do + stub_feature_flags(ci_retry_downstream_pipeline: false) + end + + it { expect(subject.details_path).to include "pipelines/#{downstream_pipeline.id}" } + end end context 'when user does not have access to read downstream pipeline' do diff --git a/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb new file mode 100644 index 00000000000..6c1f56de840 --- /dev/null +++ b/spec/lib/gitlab/ci/tags/bulk_insert_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Tags::BulkInsert do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be_with_refind(:job) { create(:ci_build, :unique_name, pipeline: pipeline, project: project) } + let_it_be_with_refind(:other_job) { create(:ci_build, :unique_name, pipeline: pipeline, project: project) } + let_it_be_with_refind(:bridge) { create(:ci_bridge, pipeline: pipeline, project: project) } + + let(:statuses) { [job, bridge, other_job] } + + subject(:service) { described_class.new(statuses, tags_list) } + + describe '#insert!' do + context 'without tags' do + let(:tags_list) { {} } + + it { expect(service.insert!).to be_falsey } + end + + context 'with tags' do + let(:tags_list) do + { + job.name => %w[tag1 tag2], + other_job.name => %w[tag2 tag3 tag4] + } + end + + it 'persists tags' do + expect(service.insert!).to be_truthy + + 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 + end + end +end diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb index 10275f33484..5ff34592b2f 100644 --- a/spec/lib/gitlab/ci/variables/builder_spec.rb +++ b/spec/lib/gitlab/ci/variables/builder_spec.rb @@ -24,15 +24,5 @@ RSpec.describe Gitlab::Ci::Variables::Builder do expect(names).to include(*keys) end end - - context 'feature flag disabled' do - before do - stub_feature_flags(ci_predefined_vars_in_builder: false) - end - - it 'returns no variables' do - expect(subject.map { |env| env[:key] }).to be_empty - 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 f00a801286d..e8b38b21ef8 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -2139,7 +2139,7 @@ module Gitlab end end - context 'with when/rules conflict' do + context 'with when/rules' do subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute } let(:config) do @@ -2174,7 +2174,7 @@ module Gitlab } end - it_behaves_like 'returns errors', /may not be used with `rules`: when/ + it { is_expected.to be_valid } end context 'used with job-level when:delayed' do @@ -2190,7 +2190,7 @@ module Gitlab } end - it_behaves_like 'returns errors', /may not be used with `rules`: when, start_in/ + it_behaves_like 'returns errors', /may not be used with `rules`: start_in/ end end |