summaryrefslogtreecommitdiff
path: root/spec/models/ci
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/bridge_spec.rb12
-rw-r--r--spec/models/ci/build_need_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb249
-rw-r--r--spec/models/ci/commit_with_pipeline_spec.rb124
-rw-r--r--spec/models/ci/group_spec.rb12
-rw-r--r--spec/models/ci/job_artifact_spec.rb33
-rw-r--r--spec/models/ci/ref_spec.rb60
7 files changed, 420 insertions, 72 deletions
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 11dcecd50ca..4f09f6f1da4 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -356,14 +356,6 @@ RSpec.describe Ci::Bridge do
describe '#dependency_variables' do
subject { bridge.dependency_variables }
- shared_context 'when ci_bridge_dependency_variables is disabled' do
- before do
- stub_feature_flags(ci_bridge_dependency_variables: false)
- end
-
- it { is_expected.to be_empty }
- end
-
context 'when downloading from previous stages' do
let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) }
let!(:bridge) { create(:ci_bridge, pipeline: pipeline, stage_idx: 1) }
@@ -374,8 +366,6 @@ RSpec.describe Ci::Bridge do
it 'inherits only dependent variables' do
expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
end
-
- it_behaves_like 'when ci_bridge_dependency_variables is disabled'
end
context 'when using needs' do
@@ -397,8 +387,6 @@ RSpec.describe Ci::Bridge do
it 'inherits only needs with artifacts variables' do
expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value)
end
-
- it_behaves_like 'when ci_bridge_dependency_variables is disabled'
end
end
end
diff --git a/spec/models/ci/build_need_spec.rb b/spec/models/ci/build_need_spec.rb
index 43cce073918..c2cf9027055 100644
--- a/spec/models/ci/build_need_spec.rb
+++ b/spec/models/ci/build_need_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::BuildNeed, model: true do
let(:build_need) { build(:ci_build_need) }
- it { is_expected.to belong_to(:build) }
+ it { is_expected.to belong_to(:build).class_name('Ci::Processable') }
it { is_expected.to validate_presence_of(:build) }
it { is_expected.to validate_presence_of(:name) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 9f412d64d56..c2029b9240b 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -717,6 +717,22 @@ RSpec.describe Ci::Build do
end
end
+ describe '#artifacts_public?' do
+ subject { build.artifacts_public? }
+
+ context 'artifacts with defaults' do
+ let(:build) { create(:ci_build, :artifacts) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'non public artifacts' do
+ let(:build) { create(:ci_build, :artifacts, :non_public_artifacts) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#artifacts_expired?' do
subject { build.artifacts_expired? }
@@ -1149,26 +1165,12 @@ RSpec.describe Ci::Build do
end
context 'when transits to skipped' do
- context 'when cd_skipped_deployment_status is disabled' do
- before do
- stub_feature_flags(cd_skipped_deployment_status: false)
- build.skip!
- end
-
- it 'transits deployment status to canceled' do
- expect(deployment).to be_canceled
- end
+ before do
+ build.skip!
end
- context 'when cd_skipped_deployment_status is enabled' do
- before do
- stub_feature_flags(cd_skipped_deployment_status: project)
- build.skip!
- end
-
- it 'transits deployment status to skipped' do
- expect(deployment).to be_skipped
- end
+ it 'transits deployment status to skipped' do
+ expect(deployment).to be_skipped
end
end
@@ -2456,6 +2458,7 @@ RSpec.describe Ci::Build do
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false },
{ key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false },
{ key: 'CI_DEFAULT_BRANCH', value: project.default_branch, public: true, masked: false },
+ { key: 'CI_PROJECT_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false },
{ key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false },
{ key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
{ key: 'CI_DEPENDENCY_PROXY_SERVER', value: "#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}", public: true, masked: false },
@@ -2986,7 +2989,7 @@ RSpec.describe Ci::Build do
let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } }
before do
- expect_any_instance_of(Project).to receive(:ci_config_path) { 'custom' }
+ project.update!(ci_config_path: 'custom')
end
it { is_expected.to include(ci_config_path) }
@@ -4343,7 +4346,7 @@ RSpec.describe Ci::Build do
end
describe '#supported_runner?' do
- let_it_be(:build) { create(:ci_build) }
+ let_it_be_with_refind(:build) { create(:ci_build) }
subject { build.supported_runner?(runner_features) }
@@ -4408,6 +4411,41 @@ RSpec.describe Ci::Build do
it { is_expected.to be_falsey }
end
end
+
+ context 'when `return_exit_code` feature is required by build' do
+ let(:options) { { allow_failure_criteria: { exit_codes: [1] } } }
+
+ before do
+ build.update!(options: options)
+ end
+
+ context 'when runner provides given feature' do
+ let(:runner_features) { { return_exit_code: true } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when runner does not provide given feature' do
+ let(:runner_features) { {} }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the runner does not provide all of the required features' do
+ let(:options) do
+ {
+ allow_failure_criteria: { exit_codes: [1] },
+ artifacts: { reports: { junit: "junit.xml" } }
+ }
+ end
+
+ let(:runner_features) { { return_exit_code: true } }
+
+ it 'requires `upload_multiple_artifacts` too' do
+ is_expected.to be_falsey
+ end
+ end
+ end
end
describe '#deployment_status' do
@@ -4737,22 +4775,6 @@ RSpec.describe Ci::Build do
describe '#debug_mode?' do
subject { build.debug_mode? }
- context 'when feature is disabled' do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- end
-
- it { is_expected.to eq false }
-
- context 'when in variables' do
- before do
- create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
- end
-
- it { is_expected.to eq false }
- end
- end
-
context 'when CI_DEBUG_TRACE=true is in variables' do
context 'when in instance variables' do
before do
@@ -4807,4 +4829,159 @@ RSpec.describe Ci::Build do
it { is_expected.to eq false }
end
end
+
+ describe '#drop_with_exit_code!' do
+ let(:exit_code) { 1 }
+ let(:options) { {} }
+
+ before do
+ build.options.merge!(options)
+ build.save!
+ end
+
+ subject(:drop_with_exit_code) do
+ build.drop_with_exit_code!(:unknown_failure, exit_code)
+ end
+
+ shared_examples 'drops the build without changing allow_failure' do
+ it 'does not change allow_failure' do
+ expect { drop_with_exit_code }
+ .not_to change { build.reload.allow_failure }
+ end
+
+ it 'drops the build' do
+ expect { drop_with_exit_code }
+ .to change { build.reload.failed? }
+ end
+ end
+
+ context 'when exit_codes are not defined' do
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'when allow_failure_criteria is nil' do
+ let(:options) { { allow_failure_criteria: nil } }
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'when exit_codes is nil' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: nil
+ }
+ }
+ end
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'when exit_codes do not match' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: [2, 3, 4]
+ }
+ }
+ end
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'with matching exit codes' do
+ let(:options) do
+ { allow_failure_criteria: { exit_codes: [1, 2, 3] } }
+ end
+
+ it 'changes allow_failure' do
+ expect { drop_with_exit_code }
+ .to change { build.reload.allow_failure }
+ end
+
+ it 'drops the build' do
+ expect { drop_with_exit_code }
+ .to change { build.reload.failed? }
+ end
+
+ it 'is executed inside a transaction' do
+ expect(build).to receive(:drop!)
+ .with(:unknown_failure)
+ .and_raise(ActiveRecord::Rollback)
+
+ expect(build).to receive(:conditionally_allow_failure!)
+ .with(1)
+ .and_call_original
+
+ expect { drop_with_exit_code }
+ .not_to change { build.reload.allow_failure }
+ end
+
+ context 'when exit_code is nil' do
+ let(:exit_code) {}
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+
+ context 'when ci_allow_failure_with_exit_codes is disabled' do
+ before do
+ stub_feature_flags(ci_allow_failure_with_exit_codes: false)
+ end
+
+ it_behaves_like 'drops the build without changing allow_failure'
+ end
+ end
+ end
+
+ describe '#exit_codes_defined?' do
+ let(:options) { {} }
+
+ before do
+ build.options.merge!(options)
+ end
+
+ subject(:exit_codes_defined) do
+ build.exit_codes_defined?
+ end
+
+ context 'without allow_failure_criteria' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when exit_codes is nil' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: nil
+ }
+ }
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when exit_codes is an empty array' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: []
+ }
+ }
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when exit_codes are defined' do
+ let(:options) do
+ {
+ allow_failure_criteria: {
+ exit_codes: [5, 6]
+ }
+ }
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
end
diff --git a/spec/models/ci/commit_with_pipeline_spec.rb b/spec/models/ci/commit_with_pipeline_spec.rb
new file mode 100644
index 00000000000..4dd288bde62
--- /dev/null
+++ b/spec/models/ci/commit_with_pipeline_spec.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::CommitWithPipeline do
+ let(:project) { create(:project, :public, :repository) }
+ let(:commit) { described_class.new(project.commit) }
+
+ describe '#last_pipeline' do
+ let!(:first_pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: commit.sha,
+ status: 'success')
+ end
+
+ let!(:second_pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: commit.sha,
+ status: 'success')
+ end
+
+ it 'returns last pipeline' do
+ expect(commit.last_pipeline).to eq second_pipeline
+ end
+ end
+
+ describe '#latest_pipeline' do
+ let(:pipeline) { double }
+
+ shared_examples_for 'fetching latest pipeline' do |ref|
+ it 'returns the latest pipeline for the project' do
+ expect(commit)
+ .to receive(:latest_pipeline_for_project)
+ .with(ref, project)
+ .and_return(pipeline)
+
+ expect(result).to eq(pipeline)
+ end
+
+ it "returns the memoized pipeline for the key of #{ref}" do
+ commit.set_latest_pipeline_for_ref(ref, pipeline)
+
+ expect(commit)
+ .not_to receive(:latest_pipeline_for_project)
+
+ expect(result).to eq(pipeline)
+ end
+ end
+
+ context 'without ref argument' do
+ let(:result) { commit.latest_pipeline }
+
+ it_behaves_like 'fetching latest pipeline', nil
+ end
+
+ context 'when a particular ref is specified' do
+ let(:result) { commit.latest_pipeline('master') }
+
+ it_behaves_like 'fetching latest pipeline', 'master'
+ end
+ end
+
+ describe '#latest_pipeline_for_project' do
+ let(:project_pipelines) { double }
+ let(:pipeline_project) { double }
+ let(:pipeline) { double }
+ let(:ref) { 'master' }
+ let(:result) { commit.latest_pipeline_for_project(ref, pipeline_project) }
+
+ before do
+ allow(pipeline_project).to receive(:ci_pipelines).and_return(project_pipelines)
+ end
+
+ it 'returns the latest pipeline of the commit for the given ref and project' do
+ expect(project_pipelines)
+ .to receive(:latest_pipeline_per_commit)
+ .with(commit.id, ref)
+ .and_return(commit.id => pipeline)
+
+ expect(result).to eq(pipeline)
+ end
+ end
+
+ describe '#set_latest_pipeline_for_ref' do
+ let(:pipeline) { double }
+
+ it 'sets the latest pipeline for a given reference' do
+ commit.set_latest_pipeline_for_ref('master', pipeline)
+
+ expect(commit.latest_pipeline('master')).to eq(pipeline)
+ end
+ end
+
+ describe "#status" do
+ it 'returns the status of the latest pipeline for the given ref' do
+ expect(commit)
+ .to receive(:latest_pipeline)
+ .with('master')
+ .and_return(double(status: 'success'))
+
+ expect(commit.status('master')).to eq('success')
+ end
+
+ it 'returns nil when latest pipeline is not present for the given ref' do
+ expect(commit)
+ .to receive(:latest_pipeline)
+ .with('master')
+ .and_return(nil)
+
+ expect(commit.status('master')).to eq(nil)
+ end
+
+ it 'returns the status of the latest pipeline when no ref is given' do
+ expect(commit)
+ .to receive(:latest_pipeline)
+ .with(nil)
+ .and_return(double(status: 'success'))
+
+ expect(commit.status).to eq('success')
+ end
+ end
+end
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index c20b7e61044..6c96e659a34 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -54,6 +54,18 @@ RSpec.describe Ci::Group do
.to be_a(Gitlab::Ci::Status::Failed)
end
end
+
+ context 'when one of the commit statuses in the group is allowed to fail' do
+ let(:jobs) do
+ [create(:ci_build, :failed, :allowed_to_fail),
+ create(:ci_build, :success)]
+ end
+
+ it 'fabricates a new detailed status object' do
+ expect(subject.detailed_status(double(:user)))
+ .to be_a(Gitlab::Ci::Status::SuccessWarning)
+ end
+ end
end
describe '.fabricate' do
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index ef21ca8f100..796947be4c8 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -219,6 +219,39 @@ RSpec.describe Ci::JobArtifact do
end
end
+ describe '.unlocked' do
+ let_it_be(:job_artifact) { create(:ci_job_artifact) }
+
+ context 'with locked pipelines' do
+ before do
+ job_artifact.job.pipeline.artifacts_locked!
+ end
+
+ it 'returns an empty array' do
+ expect(described_class.unlocked).to be_empty
+ end
+ end
+
+ context 'with unlocked pipelines' do
+ before do
+ job_artifact.job.pipeline.unlocked!
+ end
+
+ it 'returns the artifact' do
+ expect(described_class.unlocked).to eq([job_artifact])
+ end
+ end
+ end
+
+ describe '.order_expired_desc' do
+ let_it_be(:first_artifact) { create(:ci_job_artifact, expire_at: 2.days.ago) }
+ let_it_be(:second_artifact) { create(:ci_job_artifact, expire_at: 1.day.ago) }
+
+ it 'returns ordered artifacts' do
+ expect(described_class.order_expired_desc).to eq([second_artifact, first_artifact])
+ end
+ end
+
describe 'callbacks' do
describe '#schedule_background_upload' do
subject { create(:ci_job_artifact, :archive) }
diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb
index cb62646532c..0a9cd5ef2ec 100644
--- a/spec/models/ci/ref_spec.rb
+++ b/spec/models/ci/ref_spec.rb
@@ -16,35 +16,49 @@ RSpec.describe Ci::Ref do
stub_const('Ci::PipelineSuccessUnlockArtifactsWorker', unlock_artifacts_worker_spy)
end
- where(:initial_state, :action, :count) do
- :unknown | :succeed! | 1
- :unknown | :do_fail! | 0
- :success | :succeed! | 1
- :success | :do_fail! | 0
- :failed | :succeed! | 1
- :failed | :do_fail! | 0
- :fixed | :succeed! | 1
- :fixed | :do_fail! | 0
- :broken | :succeed! | 1
- :broken | :do_fail! | 0
- :still_failing | :succeed | 1
- :still_failing | :do_fail | 0
- end
+ context 'pipline is locked' do
+ let!(:pipeline) { create(:ci_pipeline, ci_ref_id: ci_ref.id, locked: :artifacts_locked) }
+
+ where(:initial_state, :action, :count) do
+ :unknown | :succeed! | 1
+ :unknown | :do_fail! | 0
+ :success | :succeed! | 1
+ :success | :do_fail! | 0
+ :failed | :succeed! | 1
+ :failed | :do_fail! | 0
+ :fixed | :succeed! | 1
+ :fixed | :do_fail! | 0
+ :broken | :succeed! | 1
+ :broken | :do_fail! | 0
+ :still_failing | :succeed | 1
+ :still_failing | :do_fail | 0
+ end
- with_them do
- context "when transitioning states" do
- before do
- status_value = Ci::Ref.state_machines[:status].states[initial_state].value
- ci_ref.update!(status: status_value)
- end
+ with_them do
+ context "when transitioning states" do
+ before do
+ status_value = Ci::Ref.state_machines[:status].states[initial_state].value
+ ci_ref.update!(status: status_value)
+ end
- it 'calls unlock artifacts service' do
- ci_ref.send(action)
+ it 'calls unlock artifacts service' do
+ ci_ref.send(action)
- expect(unlock_artifacts_worker_spy).to have_received(:perform_async).exactly(count).times
+ expect(unlock_artifacts_worker_spy).to have_received(:perform_async).exactly(count).times
+ end
end
end
end
+
+ context 'pipeline is unlocked' do
+ let!(:pipeline) { create(:ci_pipeline, ci_ref_id: ci_ref.id, locked: :unlocked) }
+
+ it 'does not call unlock artifacts service' do
+ ci_ref.succeed!
+
+ expect(unlock_artifacts_worker_spy).not_to have_received(:perform_async)
+ end
+ end
end
end