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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/bridge_spec.rb14
-rw-r--r--spec/models/ci/build_dependencies_spec.rb4
-rw-r--r--spec/models/ci/build_metadata_spec.rb13
-rw-r--r--spec/models/ci/build_runner_session_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb249
-rw-r--r--spec/models/ci/daily_build_group_report_result_spec.rb2
-rw-r--r--spec/models/ci/job_artifact_spec.rb14
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb2
-rw-r--r--spec/models/ci/pipeline_spec.rb311
-rw-r--r--spec/models/ci/processable_spec.rb2
-rw-r--r--spec/models/ci/runner_spec.rb65
-rw-r--r--spec/models/ci/runner_version_spec.rb9
-rw-r--r--spec/models/ci/secure_file_spec.rb1
13 files changed, 586 insertions, 102 deletions
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index cb29cce554f..40c2d62c465 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -25,6 +25,8 @@ RSpec.describe Ci::Bridge do
expect(bridge).to have_many(:sourced_pipelines)
end
+ it_behaves_like 'has ID tokens', :ci_bridge
+
it 'has one downstream pipeline' do
expect(bridge).to have_one(:sourced_pipeline)
expect(bridge).to have_one(:downstream_pipeline)
@@ -401,6 +403,18 @@ RSpec.describe Ci::Bridge do
end
end
+ describe '#downstream_project_path' do
+ context 'when trigger is defined' do
+ context 'when using variable expansion' do
+ let(:options) { { trigger: { project: 'my/$BRIDGE/project' } } }
+
+ it 'correctly expands variables' do
+ expect(bridge.downstream_project_path).to eq('my/cross/project')
+ end
+ end
+ end
+ end
+
describe '#target_ref' do
context 'when trigger is defined' do
it 'returns a ref name' do
diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb
index 91048cae064..737348765d9 100644
--- a/spec/models/ci/build_dependencies_spec.rb
+++ b/spec/models/ci/build_dependencies_spec.rb
@@ -76,8 +76,8 @@ RSpec.describe Ci::BuildDependencies do
end
describe 'jobs from specified dependencies' do
- let(:dependencies) { }
- let(:needs) { }
+ let(:dependencies) {}
+ let(:needs) {}
let!(:job) do
scheduling_type = needs.present? ? :dag : :stage
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
index 5e30f9160cd..e904463a5ca 100644
--- a/spec/models/ci/build_metadata_spec.rb
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -105,6 +105,13 @@ RSpec.describe Ci::BuildMetadata do
}
}
}
+ metadata.id_tokens = {
+ TEST_JWT_TOKEN: {
+ id_token: {
+ aud: 'https://gitlab.test'
+ }
+ }
+ }
expect(metadata).to be_valid
end
@@ -113,10 +120,14 @@ RSpec.describe Ci::BuildMetadata do
context 'when data is invalid' do
it 'returns errors' do
metadata.secrets = { DATABASE_PASSWORD: { vault: {} } }
+ metadata.id_tokens = { TEST_JWT_TOKEN: { id_token: { aud: nil } } }
aggregate_failures do
expect(metadata).to be_invalid
- expect(metadata.errors.full_messages).to eq(["Secrets must be a valid json schema"])
+ expect(metadata.errors.full_messages).to contain_exactly(
+ 'Secrets must be a valid json schema',
+ 'Id tokens must be a valid json schema'
+ )
end
end
end
diff --git a/spec/models/ci/build_runner_session_spec.rb b/spec/models/ci/build_runner_session_spec.rb
index 601c6ad26f9..ed5ed456d7b 100644
--- a/spec/models/ci/build_runner_session_spec.rb
+++ b/spec/models/ci/build_runner_session_spec.rb
@@ -65,7 +65,7 @@ RSpec.describe Ci::BuildRunnerSession, model: true do
end
describe '#service_specification' do
- let(:service) { 'foo'}
+ let(:service) { 'foo' }
let(:port) { 80 }
let(:path) { 'path' }
let(:subprotocols) { nil }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index e0166ba64a4..b865688d370 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -3,6 +3,9 @@
require 'spec_helper'
RSpec.describe Ci::Build do
+ include Ci::TemplateHelpers
+ include AfterNextHelpers
+
let_it_be(:user) { create(:user) }
let_it_be(:group, reload: true) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :repository, group: group) }
@@ -59,11 +62,36 @@ RSpec.describe Ci::Build do
describe 'callbacks' do
context 'when running after_create callback' do
- it 'triggers asynchronous build hooks worker' do
- expect(BuildHooksWorker).to receive(:perform_async)
+ it 'executes hooks' do
+ expect_next(described_class).to receive(:execute_hooks)
create(:ci_build)
end
+
+ context 'when the execute_build_hooks_inline flag is disabled' do
+ before do
+ stub_feature_flags(execute_build_hooks_inline: false)
+ end
+
+ it 'uses the old job hooks worker' do
+ expect(::BuildHooksWorker).to receive(:perform_async).with(Ci::Build)
+
+ create(:ci_build)
+ end
+ end
+
+ context 'when the execute_build_hooks_inline flag is enabled for a project' do
+ before do
+ stub_feature_flags(execute_build_hooks_inline: project)
+ end
+
+ it 'executes hooks inline' do
+ expect(::BuildHooksWorker).not_to receive(:perform_async)
+ expect_next(described_class).to receive(:execute_hooks)
+
+ create(:ci_build, project: project)
+ end
+ end
end
end
@@ -81,6 +109,8 @@ RSpec.describe Ci::Build do
end
end
+ it_behaves_like 'has ID tokens', :ci_build
+
describe '.manual_actions' do
let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) }
let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) }
@@ -1289,7 +1319,7 @@ RSpec.describe Ci::Build do
let(:subject) { build.hide_secrets(data) }
context 'hide runners token' do
- let(:data) { "new #{project.runners_token} data"}
+ let(:data) { "new #{project.runners_token} data" }
it { is_expected.to match(/^new x+ data$/) }
@@ -1303,7 +1333,7 @@ RSpec.describe Ci::Build do
end
context 'hide build token' do
- let(:data) { "new #{build.token} data"}
+ let(:data) { "new #{build.token} data" }
it { is_expected.to match(/^new x+ data$/) }
@@ -1335,6 +1365,43 @@ RSpec.describe Ci::Build do
end
end
+ describe 'state transition metrics' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { build.send(event) }
+
+ where(:ff_enabled, :state, :report_count, :trait) do
+ true | :success! | 1 | :sast
+ true | :cancel! | 1 | :sast
+ true | :drop! | 2 | :multiple_report_artifacts
+ true | :success! | 0 | :allowed_to_fail
+ true | :skip! | 0 | :pending
+ false | :success! | 0 | :sast
+ end
+
+ with_them do
+ let(:build) { create(:ci_build, trait, project: project, pipeline: pipeline) }
+ let(:event) { state }
+
+ context "when transitioning to #{params[:state]}" do
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ stub_feature_flags(report_artifact_build_completed_metrics_on_build_completion: ff_enabled)
+ end
+
+ it 'increments build_completed_report_type metric' do
+ expect(
+ ::Gitlab::Ci::Artifacts::Metrics
+ ).to receive(
+ :build_completed_report_type_counter
+ ).exactly(report_count).times.and_call_original
+
+ subject
+ end
+ end
+ end
+ end
+
describe 'state transition as a deployable' do
subject { build.send(event) }
@@ -1518,8 +1585,8 @@ RSpec.describe Ci::Build do
end
end
- describe '#environment_deployment_tier' do
- subject { build.environment_deployment_tier }
+ describe '#environment_tier_from_options' do
+ subject { build.environment_tier_from_options }
let(:build) { described_class.new(options: options) }
let(:options) { { environment: { deployment_tier: 'production' } } }
@@ -1533,6 +1600,30 @@ RSpec.describe Ci::Build do
end
end
+ describe '#environment_tier' do
+ subject { build.environment_tier }
+
+ let(:options) { { environment: { deployment_tier: 'production' } } }
+ let!(:environment) { create(:environment, name: 'production', tier: 'development', project: project) }
+ let(:build) { described_class.new(options: options, environment: 'production', project: project) }
+
+ it { is_expected.to eq('production') }
+
+ context 'when options does not include deployment_tier' do
+ let(:options) { { environment: { name: 'production' } } }
+
+ it 'uses tier from environment' do
+ is_expected.to eq('development')
+ end
+
+ context 'when persisted environment is absent' do
+ let(:environment) { nil }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
describe 'environment' do
describe '#has_environment?' do
subject { build.has_environment? }
@@ -1601,20 +1692,18 @@ RSpec.describe Ci::Build do
end
it 'returns an expanded environment name with a list of variables' do
- expect(build).to receive(:simple_variables).once.and_call_original
-
is_expected.to eq('review/host')
end
context 'when build metadata has already persisted the expanded environment name' do
before do
- build.metadata.expanded_environment_name = 'review/host'
+ build.metadata.expanded_environment_name = 'review/foo'
end
it 'returns a persisted expanded environment name without a list of variables' do
expect(build).not_to receive(:simple_variables)
- is_expected.to eq('review/host')
+ is_expected.to eq('review/foo')
end
end
end
@@ -1642,14 +1731,6 @@ RSpec.describe Ci::Build do
end
it { is_expected.to eq('review/master') }
-
- context 'when the FF ci_expand_environment_name_and_url is disabled' do
- before do
- stub_feature_flags(ci_expand_environment_name_and_url: false)
- end
-
- it { is_expected.to eq('review/${CI_COMMIT_REF_NAME}') }
- end
end
end
@@ -1693,7 +1774,7 @@ RSpec.describe Ci::Build do
end
context 'with a dynamic value' do
- let(:namespace) { 'deploy-$CI_COMMIT_REF_NAME'}
+ let(:namespace) { 'deploy-$CI_COMMIT_REF_NAME' }
it { is_expected.to eq 'deploy-master' }
end
@@ -1806,6 +1887,21 @@ RSpec.describe Ci::Build do
end
context 'build is erasable' do
+ context 'logging erase' do
+ let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts) }
+
+ it 'logs erased artifacts' do
+ expect(Gitlab::Ci::Artifacts::Logger)
+ .to receive(:log_deleted)
+ .with(
+ match_array(build.job_artifacts.to_a),
+ 'Ci::Build#erase'
+ )
+
+ build.erase
+ end
+ end
+
context 'when project is undergoing stats refresh' do
let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts) }
@@ -1908,7 +2004,14 @@ RSpec.describe Ci::Build do
end
end
- it "erases erasable artifacts" do
+ it "erases erasable artifacts and logs them" do
+ expect(Gitlab::Ci::Artifacts::Logger)
+ .to receive(:log_deleted)
+ .with(
+ match_array(build.job_artifacts.erasable.to_a),
+ 'Ci::Build#erase_erasable_artifacts!'
+ )
+
subject
expect(build.job_artifacts.erasable).to be_empty
@@ -2627,7 +2730,7 @@ RSpec.describe Ci::Build do
build.update_columns(token_encrypted: nil)
end
- it { is_expected.to be_nil}
+ it { is_expected.to be_nil }
end
end
@@ -2812,6 +2915,7 @@ RSpec.describe Ci::Build do
public: true,
masked: false },
{ key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
+ { key: 'CI_TEMPLATE_REGISTRY_HOST', value: template_registry_host, public: true, masked: false },
{ key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
{ key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
{ key: 'CI_PIPELINE_CREATED_AT', value: pipeline.created_at.iso8601, public: true, masked: false },
@@ -2929,7 +3033,7 @@ RSpec.describe Ci::Build do
let(:expected_variables) do
predefined_variables.map { |variable| variable.fetch(:key) } +
%w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
- CI_ENVIRONMENT_TIER CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL]
+ CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_TIER CI_ENVIRONMENT_URL]
end
before do
@@ -3096,6 +3200,16 @@ RSpec.describe Ci::Build do
end
end
+ context 'when environment_tier is updated in options' do
+ before do
+ build.update!(options: { environment: { name: 'production', deployment_tier: 'development' } })
+ end
+
+ it 'uses tier from options' do
+ is_expected.to include({ key: 'CI_ENVIRONMENT_TIER', value: 'development', public: true, masked: false })
+ end
+ end
+
context 'when project has an environment specific variable' do
let(:environment_specific_variable) do
{ key: 'MY_STAGING_ONLY_VARIABLE', value: 'environment_specific_variable', public: false, masked: false }
@@ -3508,8 +3622,8 @@ RSpec.describe Ci::Build do
context 'when gitlab-deploy-token does not exist for project' do
it 'does not include deploy token variables' do
- expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil
- expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil
+ expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD' }).to be_nil
end
context 'when gitlab-deploy-token exists for group' do
@@ -3527,8 +3641,8 @@ RSpec.describe Ci::Build do
end
it 'does not include deploy token variables' do
- expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil
- expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil
+ expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD' }).to be_nil
end
end
end
@@ -3559,10 +3673,10 @@ RSpec.describe Ci::Build do
context 'when harbor_integration does not exist' do
it 'does not include harbor variables' do
- expect(subject.find { |v| v[:key] == 'HARBOR_URL'}).to be_nil
- expect(subject.find { |v| v[:key] == 'HARBOR_PROJECT_NAME'}).to be_nil
- expect(subject.find { |v| v[:key] == 'HARBOR_USERNAME'}).to be_nil
- expect(subject.find { |v| v[:key] == 'HARBOR_PASSWORD'}).to be_nil
+ expect(subject.find { |v| v[:key] == 'HARBOR_URL' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'HARBOR_PROJECT_NAME' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'HARBOR_USERNAME' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'HARBOR_PASSWORD' }).to be_nil
end
end
end
@@ -3807,8 +3921,20 @@ RSpec.describe Ci::Build do
build.enqueue
end
- it 'queues BuildHooksWorker' do
- expect(BuildHooksWorker).to receive(:perform_async).with(build)
+ context 'when the execute_build_hooks_inline flag is disabled' do
+ before do
+ stub_feature_flags(execute_build_hooks_inline: false)
+ end
+
+ it 'queues BuildHooksWorker' do
+ expect(BuildHooksWorker).to receive(:perform_async).with(build)
+
+ build.enqueue
+ end
+ end
+
+ it 'executes hooks' do
+ expect(build).to receive(:execute_hooks)
build.enqueue
end
@@ -4526,7 +4652,7 @@ RSpec.describe Ci::Build do
end
describe '#each_report' do
- let(:report_types) { Ci::JobArtifact::COVERAGE_REPORT_FILE_TYPES }
+ let(:report_types) { Ci::JobArtifact.file_types_for_report(:coverage) }
let!(:codequality) { create(:ci_job_artifact, :codequality, job: build) }
let!(:coverage) { create(:ci_job_artifact, :coverage_gocov_xml, job: build) }
@@ -4559,6 +4685,7 @@ RSpec.describe Ci::Build do
end
before do
+ allow(build).to receive(:execute_hooks)
stub_artifacts_object_storage
end
@@ -5499,7 +5626,7 @@ RSpec.describe Ci::Build do
build.cancel_gracefully?
end
- let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
it 'cannot cancel gracefully' do
expect(subject).to be false
@@ -5520,4 +5647,58 @@ RSpec.describe Ci::Build do
let!(:model) { create(:ci_build, user: create(:user)) }
let!(:parent) { model.user }
end
+
+ describe '#clone' do
+ let_it_be(:user) { FactoryBot.build(:user) }
+
+ context 'when given new job variables' do
+ context 'when the cloned build has an action' do
+ it 'applies the new job variables' do
+ build = create(:ci_build, :actionable)
+ create(:ci_job_variable, job: build, key: 'TEST_KEY', value: 'old value')
+ create(:ci_job_variable, job: build, key: 'OLD_KEY', value: 'i will not live for long')
+
+ new_build = build.clone(current_user: user, new_job_variables_attributes: [
+ { key: 'TEST_KEY', value: 'new value' },
+ { key: 'NEW_KEY', value: 'exciting new value' }
+ ])
+ new_build.save!
+
+ expect(new_build.job_variables.count).to be(2)
+ expect(new_build.job_variables.pluck(:key)).to contain_exactly('TEST_KEY', 'NEW_KEY')
+ expect(new_build.job_variables.map(&:value)).to contain_exactly('new value', 'exciting new value')
+ end
+ end
+
+ context 'when the cloned build does not have an action' do
+ it 'applies the old job variables' do
+ build = create(:ci_build)
+ create(:ci_job_variable, job: build, key: 'TEST_KEY', value: 'old value')
+
+ new_build = build.clone(current_user: user, new_job_variables_attributes: [
+ { key: 'TEST_KEY', value: 'new value' }
+ ])
+ new_build.save!
+
+ expect(new_build.job_variables.count).to be(1)
+ expect(new_build.job_variables.pluck(:key)).to contain_exactly('TEST_KEY')
+ expect(new_build.job_variables.map(&:value)).to contain_exactly('old value')
+ end
+ end
+ end
+
+ context 'when not given new job variables' do
+ it 'applies the old job variables' do
+ build = create(:ci_build)
+ create(:ci_job_variable, job: build, key: 'TEST_KEY', value: 'old value')
+
+ new_build = build.clone(current_user: user)
+ new_build.save!
+
+ expect(new_build.job_variables.count).to be(1)
+ expect(new_build.job_variables.pluck(:key)).to contain_exactly('TEST_KEY')
+ expect(new_build.job_variables.map(&:value)).to contain_exactly('old value')
+ end
+ end
+ end
end
diff --git a/spec/models/ci/daily_build_group_report_result_spec.rb b/spec/models/ci/daily_build_group_report_result_spec.rb
index 43ba4c32477..d0141a1469e 100644
--- a/spec/models/ci/daily_build_group_report_result_spec.rb
+++ b/spec/models/ci/daily_build_group_report_result_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Ci::DailyBuildGroupReportResult do
- let(:daily_build_group_report_result) { build(:ci_daily_build_group_report_result)}
+ let(:daily_build_group_report_result) { build(:ci_daily_build_group_report_result) }
describe 'associations' do
it { is_expected.to belong_to(:last_pipeline) }
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index b9cac6c3f99..b996bf84529 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -128,6 +128,18 @@ RSpec.describe Ci::JobArtifact do
end
end
+ describe '.file_types_for_report' do
+ it 'returns the report file types for the report type' do
+ expect(described_class.file_types_for_report(:test)).to match_array(%w[junit])
+ end
+
+ context 'when given an unrecognized report type' do
+ it 'raises error' do
+ expect { described_class.file_types_for_report(:blah) }.to raise_error(KeyError, /blah/)
+ end
+ end
+ end
+
describe '.associated_file_types_for' do
using RSpec::Parameterized::TableSyntax
@@ -193,7 +205,7 @@ RSpec.describe Ci::JobArtifact do
it { is_expected.to be_truthy }
context 'when the job does have archived trace' do
- let!(:artifact) { }
+ let!(:artifact) {}
it { is_expected.to be_falsy }
end
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 3c295fb345b..b28b61e2b39 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -73,7 +73,7 @@ RSpec.describe Ci::PipelineSchedule do
end
context 'when there are no runnable schedules' do
- let!(:pipeline_schedule) { }
+ let!(:pipeline_schedule) {}
it 'returns an empty array' do
is_expected.to be_empty
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 6a71b2cfbed..0c28c99c113 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -844,6 +844,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it 'has 8 items' do
expect(subject.size).to eq(8)
end
+
it { expect(pipeline.sha).to start_with(subject) }
end
@@ -2162,6 +2163,60 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '#modified_paths_since' do
+ let(:project) do
+ create(:project, :custom_repo,
+ files: { 'file1.txt' => 'file 1' })
+ end
+
+ let(:user) { project.owner }
+ let(:main_branch) { project.default_branch }
+ let(:new_branch) { 'feature_x' }
+ let(:pipeline) { build(:ci_pipeline, project: project, sha: new_branch) }
+
+ subject(:modified_paths_since) { pipeline.modified_paths_since(main_branch) }
+
+ before do
+ project.repository.add_branch(user, new_branch, main_branch)
+ end
+
+ context 'when no change in the new branch' do
+ it 'returns an empty array' do
+ expect(modified_paths_since).to be_empty
+ end
+ end
+
+ context 'when adding a new file' do
+ before do
+ project.repository.create_file(user, 'file2.txt', 'file 2', message: 'Create file2.txt', branch_name: new_branch)
+ end
+
+ it 'returns the new file path' do
+ expect(modified_paths_since).to eq(['file2.txt'])
+ end
+
+ context 'and when updating an existing file' do
+ before do
+ project.repository.update_file(user, 'file1.txt', 'file 1 updated', message: 'Update file1.txt', branch_name: new_branch)
+ end
+
+ it 'returns the new and updated file paths' do
+ expect(modified_paths_since).to eq(['file1.txt', 'file2.txt'])
+ end
+ end
+ end
+
+ context 'when updating an existing file' do
+ before do
+ project.repository.update_file(user, 'file1.txt', 'file 1 updated', message: 'Update file1.txt', branch_name: new_branch)
+ end
+
+ it 'returns the updated file path' do
+ expect(modified_paths_since).to eq(['file1.txt'])
+ end
+ end
+ end
+
describe '#all_worktree_paths' do
let(:files) { { 'main.go' => '', 'mocks/mocks.go' => '' } }
let(:project) { create(:project, :custom_repo, files: files) }
@@ -2866,7 +2921,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
describe '#cancel_running' do
- subject(:latest_status) { pipeline.statuses.pluck(:status) }
+ let(:latest_status) { pipeline.statuses.pluck(:status) }
let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) }
@@ -2909,6 +2964,32 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ context 'with bridge jobs' do
+ before do
+ create(:ci_bridge, :created, pipeline: pipeline)
+
+ pipeline.cancel_running
+ end
+
+ it 'bridges are canceled' do
+ expect(pipeline.bridges.first.status).to eq 'canceled'
+ end
+ end
+
+ context 'when pipeline is not cancelable' do
+ before do
+ create(:ci_build, :canceled, stage_idx: 0, pipeline: pipeline)
+
+ pipeline.cancel_running
+ end
+
+ it 'does not send cancel signal to cancel self' do
+ expect(pipeline).not_to receive(:cancel_self_only)
+
+ pipeline.cancel_running
+ end
+ end
+
context 'preloading relations' do
let(:pipeline1) { create(:ci_empty_pipeline, :created) }
let(:pipeline2) { create(:ci_empty_pipeline, :created) }
@@ -2940,37 +3021,211 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
- context 'when the first try cannot get an exclusive lock' do
- let(:retries) { 1 }
+ shared_examples 'retries' do
+ context 'when the first try cannot get an exclusive lock' do
+ let(:retries) { 1 }
- subject(:cancel_running) { pipeline.cancel_running(retries: retries) }
+ subject { pipeline.cancel_running(retries: retries) }
- before do
- build = create(:ci_build, :running, pipeline: pipeline)
+ before do
+ create(:ci_build, :running, pipeline: pipeline)
- allow(pipeline.cancelable_statuses).to receive(:find_in_batches).and_yield([build])
+ stub_first_cancel_call_fails
+ end
+
+ it 'retries again and cancels the build' do
+ subject
+
+ expect(latest_status).to contain_exactly('canceled')
+ end
+ context 'when the retries parameter is 0' do
+ let(:retries) { 0 }
+
+ it 'raises error' do
+ expect { subject }.to raise_error(ActiveRecord::StaleObjectError)
+ end
+ end
+ end
+
+ def stub_first_cancel_call_fails
call_count = 0
- allow(build).to receive(:cancel).and_wrap_original do |original, *args|
- call_count >= retries ? raise(ActiveRecord::StaleObjectError) : original.call(*args)
- call_count += 1
+ allow_next_found_instance_of(Ci::Build) do |build|
+ allow(build).to receive(:cancel).and_wrap_original do |original, *args| # rubocop:disable RSpec/AnyInstanceOf
+ call_count >= retries ? raise(ActiveRecord::StaleObjectError) : original.call(*args)
+
+ call_count += 1
+ end
end
end
+ end
+
+ it_behaves_like 'retries'
- it 'retries again and cancels the build' do
- cancel_running
+ context 'when auto canceled' do
+ let!(:canceled_by) { create(:ci_empty_pipeline) }
- expect(latest_status).to contain_exactly('canceled')
+ before do
+ create(:ci_build, :running, pipeline: pipeline)
+
+ pipeline.cancel_running(auto_canceled_by_pipeline_id: canceled_by.id)
+ end
+
+ it 'sets auto cancel' do
+ jobs_canceled_by = pipeline.statuses.map { |s| s.auto_canceled_by.id }
+
+ expect(jobs_canceled_by).to contain_exactly(canceled_by.id)
+ expect(pipeline.auto_canceled_by.id).to eq(canceled_by.id)
end
+ end
- context 'when the retries parameter is 0' do
- let(:retries) { 0 }
+ context 'when there are child pipelines', :sidekiq_inline do
+ let_it_be(:child_pipeline) { create(:ci_empty_pipeline, :created, child_of: pipeline) }
- it 'raises error' do
- expect do
+ before do
+ project.clear_memoization(:cascade_cancel_pipelines_enabled)
+
+ pipeline.reload
+ end
+
+ context 'when cascade_to_children is true' do
+ let(:cascade_to_children) { true }
+ let(:canceled_by) { nil }
+ let(:execute_async) { true }
+
+ let(:params) do
+ {
+ cascade_to_children: cascade_to_children,
+ execute_async: execute_async
+ }.tap do |p|
+ p.merge!(auto_canceled_by_pipeline_id: canceled_by.id) if canceled_by
+ end
+ end
+
+ subject(:cancel_running) { pipeline.cancel_running(**params) }
+
+ context 'when cancelable child pipeline builds' do
+ before do
+ create(:ci_build, :created, pipeline: child_pipeline)
+ create(:ci_build, :running, pipeline: child_pipeline)
+ end
+
+ it 'cancels child builds' do
cancel_running
- end.to raise_error(ActiveRecord::StaleObjectError)
+
+ latest_status_for_child = child_pipeline.statuses.pluck(:status)
+ expect(latest_status_for_child).to eq %w(canceled canceled)
+ expect(latest_status).to eq %w(canceled)
+ end
+
+ it 'cancels bridges' do
+ create(:ci_bridge, :created, pipeline: pipeline)
+ create(:ci_bridge, :created, pipeline: child_pipeline)
+
+ cancel_running
+
+ expect(pipeline.bridges.reload.first.status).to eq 'canceled'
+ expect(child_pipeline.bridges.reload.first.status).to eq 'canceled'
+ end
+
+ context 'with nested child pipelines' do
+ let!(:nested_child_pipeline) { create(:ci_empty_pipeline, :created, child_of: child_pipeline) }
+ let!(:nested_child_pipeline_build) { create(:ci_build, :created, pipeline: nested_child_pipeline) }
+
+ it 'cancels them' do
+ cancel_running
+
+ expect(nested_child_pipeline.reload.status).to eq 'canceled'
+ expect(nested_child_pipeline_build.reload.status).to eq 'canceled'
+ end
+ end
+
+ context 'when auto canceled' do
+ let(:canceled_by) { create(:ci_empty_pipeline) }
+
+ it 'sets auto cancel' do
+ cancel_running
+
+ pipeline.reload
+
+ jobs_canceled_by_ids = pipeline.statuses.map(&:auto_canceled_by_id)
+ child_pipelines_canceled_by_ids = pipeline.child_pipelines.map(&:auto_canceled_by_id)
+ child_pipelines_jobs_canceled_by_ids = pipeline.child_pipelines.map(&:statuses).flatten.map(&:auto_canceled_by_id)
+
+ expect(jobs_canceled_by_ids).to contain_exactly(canceled_by.id)
+ expect(pipeline.auto_canceled_by_id).to eq(canceled_by.id)
+ expect(child_pipelines_canceled_by_ids).to contain_exactly(canceled_by.id)
+ expect(child_pipelines_jobs_canceled_by_ids).to contain_exactly(canceled_by.id, canceled_by.id)
+ end
+ end
+
+ context 'when execute_async is false' do
+ let(:execute_async) { false }
+
+ it 'runs sync' do
+ expect(::Ci::CancelPipelineWorker).not_to receive(:perform_async)
+
+ cancel_running
+ end
+
+ it 'cancels children' do
+ cancel_running
+
+ latest_status_for_child = child_pipeline.statuses.pluck(:status)
+ expect(latest_status_for_child).to eq %w(canceled canceled)
+ expect(latest_status).to eq %w(canceled)
+ end
+
+ context 'with nested child pipelines' do
+ let!(:nested_child_pipeline) { create(:ci_empty_pipeline, :created, child_of: child_pipeline) }
+ let!(:nested_child_pipeline_build) { create(:ci_build, :created, pipeline: nested_child_pipeline) }
+
+ it 'cancels them' do
+ cancel_running
+
+ expect(nested_child_pipeline.reload.status).to eq 'canceled'
+ expect(nested_child_pipeline_build.reload.status).to eq 'canceled'
+ end
+ end
+ end
+ end
+
+ it 'does not cancel uncancelable child pipeline builds' do
+ create(:ci_build, :failed, pipeline: child_pipeline)
+
+ cancel_running
+
+ latest_status_for_child = child_pipeline.statuses.pluck(:status)
+ expect(latest_status_for_child).to eq %w(failed)
+ expect(latest_status).to eq %w(canceled)
+ end
+ end
+
+ context 'when cascade_to_children is false' do
+ let(:cascade_to_children) { false }
+
+ subject(:cancel_running) { pipeline.cancel_running(cascade_to_children: cascade_to_children) }
+
+ it 'does not cancel cancelable child pipeline builds' do
+ create(:ci_build, :created, pipeline: child_pipeline)
+ create(:ci_build, :running, pipeline: child_pipeline)
+
+ cancel_running
+
+ latest_status_for_child = child_pipeline.statuses.order_id_desc.pluck(:status)
+ expect(latest_status_for_child).to eq %w(running created)
+ expect(latest_status).to eq %w(canceled)
+ end
+
+ it 'does not cancel uncancelable child pipeline builds' do
+ create(:ci_build, :failed, pipeline: child_pipeline)
+
+ cancel_running
+
+ latest_status_for_child = child_pipeline.statuses.pluck(:status)
+ expect(latest_status_for_child).to eq %w(failed)
+ expect(latest_status).to eq %w(canceled)
end
end
end
@@ -3352,7 +3607,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
context 'when pipeline is a triggered pipeline' do
- let!(:upstream) { create(:ci_pipeline, project: create(:project), upstream_of: pipeline)}
+ let!(:upstream) { create(:ci_pipeline, project: create(:project), upstream_of: pipeline) }
it 'returns self id' do
expect(subject).to contain_exactly(pipeline.id)
@@ -4335,24 +4590,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
end
-
- describe '#find_stage_by_name' do
- subject { pipeline.find_stage_by_name!(stage_name) }
-
- context 'when stage exists' do
- it { is_expected.to eq(stage) }
- end
-
- context 'when stage does not exist' do
- let(:stage_name) { 'build' }
-
- it 'raises an ActiveRecord exception' do
- expect do
- subject
- end.to raise_exception(ActiveRecord::RecordNotFound)
- end
- end
- end
end
describe '#full_error_messages' do
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index 789ae3a2ccc..127a1417d9e 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe Ci::Processable do
job_artifacts_network_referee job_artifacts_dotenv
job_artifacts_cobertura needs job_artifacts_accessibility
job_artifacts_requirements job_artifacts_coverage_fuzzing
- job_artifacts_api_fuzzing terraform_state_versions].freeze
+ job_artifacts_api_fuzzing terraform_state_versions job_artifacts_cyclonedx].freeze
end
let(:ignore_accessors) do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 2fbfbbaf830..ae8748f8ae3 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -489,7 +489,7 @@ RSpec.describe Ci::Runner do
let!(:runner3) { create(:ci_runner, :instance, contacted_at: 1.month.ago, created_at: 2.months.ago) }
let!(:runner4) { create(:ci_runner, :instance, contacted_at: 1.month.ago, created_at: 3.months.ago) }
- it { is_expected.to eq([runner1, runner3, runner4])}
+ it { is_expected.to eq([runner1, runner3, runner4]) }
end
describe '.active' do
@@ -552,6 +552,10 @@ RSpec.describe Ci::Runner do
allow_any_instance_of(described_class).to receive(:cached_attribute).and_call_original
allow_any_instance_of(described_class).to receive(:cached_attribute)
.with(:platform).and_return("darwin")
+ allow_any_instance_of(described_class).to receive(:cached_attribute)
+ .with(:version).and_return("14.0.0")
+
+ allow(Ci::Runners::ProcessRunnerVersionUpdateWorker).to receive(:perform_async).once
end
context 'table tests' do
@@ -623,6 +627,10 @@ RSpec.describe Ci::Runner do
allow_any_instance_of(described_class).to receive(:cached_attribute).and_call_original
allow_any_instance_of(described_class).to receive(:cached_attribute)
.with(:platform).and_return("darwin")
+ allow_any_instance_of(described_class).to receive(:cached_attribute)
+ .with(:version).and_return("14.0.0")
+
+ allow(Ci::Runners::ProcessRunnerVersionUpdateWorker).to receive(:perform_async).once
end
context 'no cache value' do
@@ -693,19 +701,6 @@ RSpec.describe Ci::Runner do
it { is_expected.to eq([runner1]) }
end
- describe '#tick_runner_queue' do
- it 'sticks the runner to the primary and calls the original method' do
- runner = create(:ci_runner)
-
- expect(described_class.sticking).to receive(:stick)
- .with(:runner, runner.id)
-
- expect(Gitlab::Workhorse).to receive(:set_key_and_notify)
-
- runner.tick_runner_queue
- end
- end
-
describe '#matches_build?' do
using RSpec::Parameterized::TableSyntax
@@ -866,7 +861,7 @@ RSpec.describe Ci::Runner do
describe '#status' do
let(:runner) { build(:ci_runner, :instance, created_at: 4.months.ago) }
- let(:legacy_mode) { }
+ let(:legacy_mode) {}
subject { runner.status(legacy_mode) }
@@ -989,6 +984,16 @@ RSpec.describe Ci::Runner do
it 'returns a new last_update value' do
expect(runner.tick_runner_queue).not_to be_empty
end
+
+ it 'sticks the runner to the primary and calls the original method' do
+ runner = create(:ci_runner)
+
+ expect(described_class.sticking).to receive(:stick).with(:runner, runner.id)
+
+ expect(Gitlab::Workhorse).to receive(:set_key_and_notify)
+
+ runner.tick_runner_queue
+ end
end
describe '#ensure_runner_queue_value' do
@@ -1055,14 +1060,19 @@ RSpec.describe Ci::Runner do
it 'updates cache' do
expect_redis_update
+ expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).not_to receive(:perform_async)
heartbeat
+
+ expect(runner.runner_version).to be_nil
end
end
context 'when database was not updated recently' do
before do
runner.contacted_at = 2.hours.ago
+
+ allow(Ci::Runners::ProcessRunnerVersionUpdateWorker).to receive(:perform_async)
end
context 'with invalid runner' do
@@ -1075,12 +1085,25 @@ RSpec.describe Ci::Runner do
expect_redis_update
does_db_update
+
+ expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).to have_received(:perform_async).once
+ end
+ end
+
+ context 'with unchanged runner version' do
+ let(:runner) { create(:ci_runner, version: version) }
+
+ it 'does not schedule ci_runner_versions update' do
+ heartbeat
+
+ expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).not_to have_received(:perform_async)
end
end
it 'updates redis cache and database' do
expect_redis_update
does_db_update
+ expect(Ci::Runners::ProcessRunnerVersionUpdateWorker).to have_received(:perform_async).once
end
%w(custom shell docker docker-windows docker-ssh ssh parallels virtualbox docker+machine docker-ssh+machine kubernetes some-unknown-type).each do |executor|
@@ -1795,11 +1818,21 @@ RSpec.describe Ci::Runner do
end
context ':recommended' do
- let(:upgrade_status) { :recommended}
+ let(:upgrade_status) { :recommended }
it 'returns runners whose version is assigned :recommended' do
is_expected.to contain_exactly(runner_14_1_0)
end
end
+
+ describe 'composed with other scopes' do
+ subject { described_class.active(false).with_upgrade_status(:available) }
+
+ let(:inactive_runner_14_0_0) { create(:ci_runner, version: '14.0.0', active: false) }
+
+ it 'returns runner matching the composed scope' do
+ is_expected.to contain_exactly(inactive_runner_14_0_0)
+ end
+ end
end
end
diff --git a/spec/models/ci/runner_version_spec.rb b/spec/models/ci/runner_version_spec.rb
index d3395942a39..7a4b2e8f21e 100644
--- a/spec/models/ci/runner_version_spec.rb
+++ b/spec/models/ci/runner_version_spec.rb
@@ -27,16 +27,11 @@ RSpec.describe Ci::RunnerVersion do
create(:ci_runner_version, version: 'abc456', status: :available)
end
- let_it_be(:runner_version_unknown) do
- create(:ci_runner_version, version: 'abc567', status: :unknown)
- end
-
- it 'contains any runner version that is not already recommended' do
+ it 'contains any valid or unprocessed runner version that is not already recommended' do
is_expected.to match_array([
runner_version_nil,
runner_version_not_available,
- runner_version_available,
- runner_version_unknown
+ runner_version_available
])
end
end
diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb
index a3f1c7b7ef7..e47efff5dfd 100644
--- a/spec/models/ci/secure_file_spec.rb
+++ b/spec/models/ci/secure_file_spec.rb
@@ -26,6 +26,7 @@ RSpec.describe Ci::SecureFile do
it { is_expected.to validate_presence_of(:file_store) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_presence_of(:project_id) }
+
context 'unique filename' do
let_it_be(:project1) { create(:project) }