diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /spec/models/ci | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'spec/models/ci')
-rw-r--r-- | spec/models/ci/build_spec.rb | 49 | ||||
-rw-r--r-- | spec/models/ci/job_artifact_spec.rb | 4 | ||||
-rw-r--r-- | spec/models/ci/namespace_mirror_spec.rb | 94 | ||||
-rw-r--r-- | spec/models/ci/pending_build_spec.rb | 12 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 96 | ||||
-rw-r--r-- | spec/models/ci/project_mirror_spec.rb | 36 | ||||
-rw-r--r-- | spec/models/ci/runner_namespace_spec.rb | 6 | ||||
-rw-r--r-- | spec/models/ci/runner_project_spec.rb | 6 | ||||
-rw-r--r-- | spec/models/ci/runner_spec.rb | 271 | ||||
-rw-r--r-- | spec/models/ci/stage_spec.rb | 12 |
10 files changed, 483 insertions, 103 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index b7de8ca4337..b9a12339e61 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -29,7 +29,7 @@ RSpec.describe Ci::Build do it { is_expected.to have_one(:deployment) } it { is_expected.to have_one(:runner_session) } it { is_expected.to have_one(:trace_metadata) } - it { is_expected.to have_many(:terraform_state_versions).dependent(:nullify).inverse_of(:build) } + it { is_expected.to have_many(:terraform_state_versions).inverse_of(:build) } it { is_expected.to validate_presence_of(:ref) } @@ -1994,6 +1994,14 @@ RSpec.describe Ci::Build do it { is_expected.not_to be_retryable } end + + context 'when deployment is rejected' do + before do + build.drop!(:deployment_rejected) + end + + it { is_expected.not_to be_retryable } + end end end @@ -2498,7 +2506,7 @@ RSpec.describe Ci::Build do it { is_expected.to start_with(project.web_url[0..6]) } it { is_expected.to include(build.token) } it { is_expected.to include('gitlab-ci-token') } - it { is_expected.to include(project.web_url[7..-1]) } + it { is_expected.to include(project.web_url[7..]) } end context 'when token is empty' do @@ -3421,10 +3429,6 @@ RSpec.describe Ci::Build do end describe '#scoped_variables' do - before do - pipeline.clear_memoization(:predefined_vars_in_builder_enabled) - end - it 'records a prometheus metric' do histogram = double(:histogram) expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_builder_scoped_variables_histogram) @@ -3522,22 +3526,6 @@ RSpec.describe Ci::Build do build.scoped_variables end - - context 'when ci builder feature flag is disabled' do - before do - stub_feature_flags(ci_predefined_vars_in_builder: false) - end - - it 'does not delegate to the variable builders' do - expect_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder| - expect(builder).not_to receive(:predefined_variables) - end - - build.scoped_variables - end - - it_behaves_like 'calculates scoped_variables' - end end describe '#simple_variables_without_dependencies' do @@ -3782,6 +3770,12 @@ RSpec.describe Ci::Build do build.enqueue end + + it 'queues BuildHooksWorker' do + expect(BuildHooksWorker).to receive(:perform_async).with(build.id) + + build.enqueue + end end describe 'state transition: pending: :running' do @@ -4474,7 +4468,7 @@ RSpec.describe Ci::Build do 'create' => 0, 'update' => 1, 'delete' => 0, - 'job_name' => build.options.dig(:artifacts, :name).to_s + 'job_name' => build.name ) ) ) @@ -5423,4 +5417,13 @@ RSpec.describe Ci::Build do expect(subject).to be true end end + + it_behaves_like 'it has loose foreign keys' do + let(:factory_name) { :ci_build } + end + + it_behaves_like 'cleanup by a loose foreign key' do + let!(:model) { create(:ci_build, user: create(:user)) } + let!(:parent) { model.user } + end end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index d63f87e8943..38061e0975f 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -700,4 +700,8 @@ RSpec.describe Ci::JobArtifact do when changes or new entries are made. MSG end + + it_behaves_like 'it has loose foreign keys' do + let(:factory_name) { :ci_job_artifact } + end end diff --git a/spec/models/ci/namespace_mirror_spec.rb b/spec/models/ci/namespace_mirror_spec.rb new file mode 100644 index 00000000000..b4c71f51377 --- /dev/null +++ b/spec/models/ci/namespace_mirror_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::NamespaceMirror do + let!(:group1) { create(:group) } + let!(:group2) { create(:group, parent: group1) } + let!(:group3) { create(:group, parent: group2) } + let!(:group4) { create(:group, parent: group3) } + + describe '.sync!' do + let!(:event) { namespace.sync_events.create! } + + subject(:sync) { described_class.sync!(event.reload) } + + context 'when namespace hierarchy does not exist in the first place' do + let(:namespace) { group3 } + + it 'creates the hierarchy' do + expect { sync }.to change { described_class.count }.from(0).to(1) + + expect(namespace.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id]) + end + end + + context 'when namespace hierarchy does already exist' do + let(:namespace) { group3 } + + before do + described_class.create!(namespace: namespace, traversal_ids: [namespace.id]) + end + + it 'updates the hierarchy' do + expect { sync }.not_to change { described_class.count } + + expect(namespace.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id, group2.id, group3.id]) + end + end + + # I did not extract this context to a `shared_context` because the behavior will change + # after implementing the TODO in `Ci::NamespaceMirror.sync!` + context 'changing the middle namespace' do + let(:namespace) { group2 } + + before do + described_class.create!(namespace_id: group1.id, traversal_ids: [group1.id]) + described_class.create!(namespace_id: group2.id, traversal_ids: [group1.id, group2.id]) + described_class.create!(namespace_id: group3.id, traversal_ids: [group1.id, group2.id, group3.id]) + described_class.create!(namespace_id: group4.id, traversal_ids: [group1.id, group2.id, group3.id, group4.id]) + + group2.update!(parent: nil) + end + + it 'updates hierarchies for the base but wait for events for the children' do + expect { sync }.not_to change { described_class.count } + + expect(group1.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id]) + expect(group2.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id]) + expect(group3.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id, group3.id]) + expect(group4.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id, group3.id, group4.id]) + end + end + + context 'when the FFs sync_traversal_ids, use_traversal_ids and use_traversal_ids_for_ancestors are disabled' do + before do + stub_feature_flags(sync_traversal_ids: false, + use_traversal_ids: false, + use_traversal_ids_for_ancestors: false) + end + + context 'changing the middle namespace' do + let(:namespace) { group2 } + + before do + described_class.create!(namespace_id: group1.id, traversal_ids: [group1.id]) + described_class.create!(namespace_id: group2.id, traversal_ids: [group1.id, group2.id]) + described_class.create!(namespace_id: group3.id, traversal_ids: [group1.id, group2.id, group3.id]) + described_class.create!(namespace_id: group4.id, traversal_ids: [group1.id, group2.id, group3.id, group4.id]) + + group2.update!(parent: nil) + end + + it 'updates hierarchies for the base and descendants' do + expect { sync }.not_to change { described_class.count } + + expect(group1.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group1.id]) + expect(group2.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id]) + expect(group3.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id, group3.id]) + expect(group4.reload.ci_namespace_mirror).to have_attributes(traversal_ids: [group2.id, group3.id, group4.id]) + end + end + end + end +end diff --git a/spec/models/ci/pending_build_spec.rb b/spec/models/ci/pending_build_spec.rb index ad711f5622f..abf0fb443bb 100644 --- a/spec/models/ci/pending_build_spec.rb +++ b/spec/models/ci/pending_build_spec.rb @@ -118,7 +118,7 @@ RSpec.describe Ci::PendingBuild do project.shared_runners_enabled = true end - context 'when ci_pending_builds_maintain_shared_runners_data is enabled' do + context 'when ci_pending_builds_maintain_denormalized_data is enabled' do it 'sets instance_runners_enabled to true' do described_class.upsert_from_build!(build) @@ -150,9 +150,9 @@ RSpec.describe Ci::PendingBuild do end end - context 'when ci_pending_builds_maintain_shared_runners_data is disabled' do + context 'when ci_pending_builds_maintain_denormalized_data is disabled' do before do - stub_feature_flags(ci_pending_builds_maintain_shared_runners_data: false) + stub_feature_flags(ci_pending_builds_maintain_denormalized_data: false) end it 'sets instance_runners_enabled to false' do @@ -168,7 +168,7 @@ RSpec.describe Ci::PendingBuild do subject(:ci_pending_build) { described_class.last } - context 'when ci_pending_builds_maintain_tags_data is enabled' do + context 'when ci_pending_builds_maintain_denormalized_data is enabled' do it 'sets tag_ids' do described_class.upsert_from_build!(build) @@ -176,9 +176,9 @@ RSpec.describe Ci::PendingBuild do end end - context 'when ci_pending_builds_maintain_tags_data is disabled' do + context 'when ci_pending_builds_maintain_denormalized_data is disabled' do before do - stub_feature_flags(ci_pending_builds_maintain_tags_data: false) + stub_feature_flags(ci_pending_builds_maintain_denormalized_data: false) end it 'does not set tag_ids' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index e573a6ef780..fd9970699d7 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -28,6 +28,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:builds) } + it { is_expected.to have_many(:statuses_order_id_desc) } it { is_expected.to have_many(:bridges) } it { is_expected.to have_many(:job_artifacts).through(:builds) } it { is_expected.to have_many(:auto_canceled_pipelines) } @@ -35,8 +36,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do it { is_expected.to have_many(:sourced_pipelines) } it { is_expected.to have_many(:triggered_pipelines) } it { is_expected.to have_many(:pipeline_artifacts) } - it { is_expected.to have_many(:package_build_infos).dependent(:nullify).inverse_of(:pipeline) } - it { is_expected.to have_many(:package_file_build_infos).dependent(:nullify).inverse_of(:pipeline) } it { is_expected.to have_one(:chat_data) } it { is_expected.to have_one(:source_pipeline) } @@ -757,23 +756,23 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'with multiple pipelines' do before_all do create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline) - create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline) + create(:ci_build, name: "rubocop", coverage: 35, pipeline: pipeline) end it "calculates average when there are two builds with coverage" do - expect(pipeline.coverage).to eq("35.00") + expect(pipeline.coverage).to be_within(0.001).of(32.5) end it "calculates average when there are two builds with coverage and one with nil" do create(:ci_build, pipeline: pipeline) - expect(pipeline.coverage).to eq("35.00") + expect(pipeline.coverage).to be_within(0.001).of(32.5) end it "calculates average when there are two builds with coverage and one is retried" do create(:ci_build, name: "rubocop", coverage: 30, pipeline: pipeline, retried: true) - expect(pipeline.coverage).to eq("35.00") + expect(pipeline.coverage).to be_within(0.001).of(32.5) end end @@ -1358,12 +1357,26 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do describe 'synching status to Jira' do let(:worker) { ::JiraConnect::SyncBuildsWorker } - %i[prepare! run! skip! drop! succeed! cancel! block! delay!].each do |event| - context "when we call pipeline.#{event}" do - it 'triggers a Jira synch worker' do - expect(worker).to receive(:perform_async).with(pipeline.id, Integer) + context 'when Jira Connect subscription does not exist' do + it 'does not trigger a Jira synch worker' do + expect(worker).not_to receive(:perform_async) - pipeline.send(event) + pipeline.prepare! + end + end + + context 'when Jira Connect subscription exists' do + before_all do + create(:jira_connect_subscription, namespace: project.namespace) + end + + %i[prepare! run! skip! drop! succeed! cancel! block! delay!].each do |event| + context "when we call pipeline.#{event}" do + it 'triggers a Jira synch worker' do + expect(worker).to receive(:perform_async).with(pipeline.id, Integer) + + pipeline.send(event) + end end end end @@ -1503,10 +1516,30 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end describe 'pipeline caching' do - it 'performs ExpirePipelinesCacheWorker' do - expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id) + context 'when expire_job_and_pipeline_cache_synchronously is enabled' do + before do + stub_feature_flags(expire_job_and_pipeline_cache_synchronously: true) + end - pipeline.cancel + it 'executes Ci::ExpirePipelineCacheService' do + expect_next_instance_of(Ci::ExpirePipelineCacheService) do |service| + expect(service).to receive(:execute).with(pipeline) + end + + pipeline.cancel + end + end + + context 'when expire_job_and_pipeline_cache_synchronously is disabled' do + before do + stub_feature_flags(expire_job_and_pipeline_cache_synchronously: false) + end + + it 'performs ExpirePipelinesCacheWorker' do + expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id) + + pipeline.cancel + end end end @@ -3173,11 +3206,35 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do context 'when pipeline is not child nor parent' do let_it_be(:pipeline) { create(:ci_pipeline, :created) } - let_it_be(:build) { create(:ci_build, :with_deployment, :deploy_to_production, pipeline: pipeline) } + let_it_be(:build, refind: true) { create(:ci_build, :with_deployment, :deploy_to_production, pipeline: pipeline) } it 'returns just the pipeline environment' do expect(subject).to contain_exactly(build.deployment.environment) end + + context 'when deployment SHA is not matched' do + before do + build.deployment.update!(sha: 'old-sha') + end + + it 'does not return environments' do + expect(subject).to be_empty + end + end + end + + context 'when an associated environment does not have deployments' do + let_it_be(:pipeline) { create(:ci_pipeline, :created) } + let_it_be(:build) { create(:ci_build, :stop_review_app, pipeline: pipeline) } + let_it_be(:environment) { create(:environment, project: pipeline.project) } + + before_all do + build.metadata.update!(expanded_environment_name: environment.name) + end + + it 'does not return environments' do + expect(subject).to be_empty + end end context 'when pipeline is in extended family' do @@ -4611,4 +4668,13 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do expect(pipeline.authorized_cluster_agents).to contain_exactly(agent) # cached end end + + it_behaves_like 'it has loose foreign keys' do + let(:factory_name) { :ci_pipeline } + end + + it_behaves_like 'cleanup by a loose foreign key' do + let!(:model) { create(:ci_pipeline, user: create(:user)) } + let!(:parent) { model.user } + end end diff --git a/spec/models/ci/project_mirror_spec.rb b/spec/models/ci/project_mirror_spec.rb new file mode 100644 index 00000000000..199285b036c --- /dev/null +++ b/spec/models/ci/project_mirror_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::ProjectMirror do + let_it_be(:group1) { create(:group) } + let_it_be(:group2) { create(:group) } + + let!(:project) { create(:project, namespace: group2) } + + describe '.sync!' do + let!(:event) { Projects::SyncEvent.create!(project: project) } + + subject(:sync) { described_class.sync!(event.reload) } + + context 'when project hierarchy does not exist in the first place' do + it 'creates a ci_projects record' do + expect { sync }.to change { described_class.count }.from(0).to(1) + + expect(project.ci_project_mirror).to have_attributes(namespace_id: group2.id) + end + end + + context 'when project hierarchy does already exist' do + before do + described_class.create!(project_id: project.id, namespace_id: group1.id) + end + + it 'updates the related ci_projects record' do + expect { sync }.not_to change { described_class.count } + + expect(project.ci_project_mirror).to have_attributes(namespace_id: group2.id) + end + end + end +end diff --git a/spec/models/ci/runner_namespace_spec.rb b/spec/models/ci/runner_namespace_spec.rb index 4e7cf7a3cb3..41d805adb9f 100644 --- a/spec/models/ci/runner_namespace_spec.rb +++ b/spec/models/ci/runner_namespace_spec.rb @@ -4,12 +4,6 @@ require 'spec_helper' RSpec.describe Ci::RunnerNamespace do it_behaves_like 'includes Limitable concern' do - before do - skip_default_enabled_yaml_check - - stub_feature_flags(ci_runner_limits_override: false) - end - subject { build(:ci_runner_namespace, group: create(:group, :nested), runner: create(:ci_runner, :group)) } end end diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb index fef1416a84a..13369dba2cf 100644 --- a/spec/models/ci/runner_project_spec.rb +++ b/spec/models/ci/runner_project_spec.rb @@ -4,12 +4,6 @@ require 'spec_helper' RSpec.describe Ci::RunnerProject do it_behaves_like 'includes Limitable concern' do - before do - skip_default_enabled_yaml_check - - stub_feature_flags(ci_runner_limits_override: false) - end - subject { build(:ci_runner_project, project: create(:project), runner: create(:ci_runner, :project)) } end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 2e79159cc60..5142f70fa2c 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -7,10 +7,6 @@ RSpec.describe Ci::Runner do it_behaves_like 'it has loose foreign keys' do let(:factory_name) { :ci_runner } - - before do - Clusters::Applications::Runner # ensure that the referenced model is loaded - end end describe 'groups association' do @@ -298,26 +294,134 @@ RSpec.describe Ci::Runner do describe '.recent' do subject { described_class.recent } + let!(:runner1) { create(:ci_runner, :instance, contacted_at: nil, created_at: 2.months.ago) } + let!(:runner2) { create(:ci_runner, :instance, contacted_at: nil, created_at: 3.months.ago) } + 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])} + end + + describe '.active' do + subject { described_class.active(active_value) } + + let!(:runner1) { create(:ci_runner, :instance, active: false) } + let!(:runner2) { create(:ci_runner, :instance) } + + context 'with active_value set to false' do + let(:active_value) { false } + + it 'returns inactive runners' do + is_expected.to match_array([runner1]) + end + end + + context 'with active_value set to true' do + let(:active_value) { true } + + it 'returns active runners' do + is_expected.to match_array([runner2]) + end + end + end + + describe '.paused' do before do - @runner1 = create(:ci_runner, :instance, contacted_at: nil, created_at: 2.months.ago) - @runner2 = create(:ci_runner, :instance, contacted_at: nil, created_at: 3.months.ago) - @runner3 = create(:ci_runner, :instance, contacted_at: 1.month.ago, created_at: 2.months.ago) - @runner4 = create(:ci_runner, :instance, contacted_at: 1.month.ago, created_at: 3.months.ago) - @runner5 = create(:ci_runner, :instance, contacted_at: 3.months.ago, created_at: 5.months.ago) + expect(described_class).to receive(:active).with(false).and_call_original end - it { is_expected.to eq([@runner1, @runner3, @runner4])} + subject { described_class.paused } + + let!(:runner1) { create(:ci_runner, :instance, active: false) } + let!(:runner2) { create(:ci_runner, :instance) } + + it 'returns inactive runners' do + is_expected.to match_array([runner1]) + end end - describe '.online' do - subject { described_class.online } + describe '.stale' do + subject { described_class.stale } + + let!(:runner1) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago + 10.seconds) } + let!(:runner2) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago - 1.second) } + let!(:runner3) { create(:ci_runner, :instance, created_at: 3.months.ago - 1.second, contacted_at: nil) } + let!(:runner4) { create(:ci_runner, :instance, created_at: 2.months.ago, contacted_at: nil) } + + it 'returns stale runners' do + is_expected.to match_array([runner2, runner3]) + end + end + + describe '#stale?', :clean_gitlab_redis_cache do + let(:runner) { create(:ci_runner, :instance) } + + subject { runner.stale? } before do - @runner1 = create(:ci_runner, :instance, contacted_at: 2.hours.ago) - @runner2 = create(:ci_runner, :instance, contacted_at: 1.second.ago) + 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") end - it { is_expected.to eq([@runner2])} + context 'table tests' do + using RSpec::Parameterized::TableSyntax + + where(:created_at, :contacted_at, :expected_stale?) do + nil | nil | false + 3.months.ago - 1.second | 3.months.ago - 0.001.seconds | true + 3.months.ago - 1.second | 3.months.ago + 1.hour | false + 3.months.ago - 1.second | nil | true + 3.months.ago + 1.hour | nil | false + end + + with_them do + before do + runner.created_at = created_at + end + + context 'no cache value' do + before do + stub_redis_runner_contacted_at(nil) + runner.contacted_at = contacted_at + end + + specify do + is_expected.to eq(expected_stale?) + end + end + + context 'with cache value' do + before do + runner.contacted_at = contacted_at ? contacted_at + 1.week : nil + stub_redis_runner_contacted_at(contacted_at.to_s) + end + + specify do + is_expected.to eq(expected_stale?) + end + end + + def stub_redis_runner_contacted_at(value) + return unless created_at + + Gitlab::Redis::Cache.with do |redis| + cache_key = runner.send(:cache_attribute_key) + expect(redis).to receive(:get).with(cache_key) + .and_return({ contacted_at: value }.to_json).at_least(:once) + end + end + end + end + end + + describe '.online' do + subject { described_class.online } + + let!(:runner1) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) } + let!(:runner2) { create(:ci_runner, :instance, contacted_at: 1.second.ago) } + + it { is_expected.to match_array([runner2]) } end describe '#online?', :clean_gitlab_redis_cache do @@ -344,7 +448,7 @@ RSpec.describe Ci::Runner do it { is_expected.to be_falsey } end - context 'contacted long time ago time' do + context 'contacted long time ago' do before do runner.contacted_at = 1.year.ago end @@ -362,7 +466,7 @@ RSpec.describe Ci::Runner do end context 'with cache value' do - context 'contacted long time ago time' do + context 'contacted long time ago' do before do runner.contacted_at = 1.year.ago stub_redis_runner_contacted_at(1.year.ago.to_s) @@ -393,12 +497,10 @@ RSpec.describe Ci::Runner do describe '.offline' do subject { described_class.offline } - before do - @runner1 = create(:ci_runner, :instance, contacted_at: 2.hours.ago) - @runner2 = create(:ci_runner, :instance, contacted_at: 1.second.ago) - end + let!(:runner1) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) } + let!(:runner2) { create(:ci_runner, :instance, contacted_at: 1.second.ago) } - it { is_expected.to eq([@runner1])} + it { is_expected.to eq([runner1]) } end describe '#tick_runner_queue' do @@ -626,16 +728,33 @@ RSpec.describe Ci::Runner do end describe '#status' do - let(:runner) { build(:ci_runner, :instance) } + let(:runner) { build(:ci_runner, :instance, created_at: 4.months.ago) } + let(:legacy_mode) { } - subject { runner.status } + subject { runner.status(legacy_mode) } context 'never connected' do before do runner.contacted_at = nil end - it { is_expected.to eq(:not_connected) } + context 'with legacy_mode enabled' do + let(:legacy_mode) { '14.5' } + + it { is_expected.to eq(:not_connected) } + end + + context 'with legacy_mode disabled' do + it { is_expected.to eq(:stale) } + end + + context 'created recently' do + before do + runner.created_at = 1.day.ago + end + + it { is_expected.to eq(:never_contacted) } + end end context 'inactive but online' do @@ -644,7 +763,15 @@ RSpec.describe Ci::Runner do runner.active = false end - it { is_expected.to eq(:online) } + context 'with legacy_mode enabled' do + let(:legacy_mode) { '14.5' } + + it { is_expected.to eq(:paused) } + end + + context 'with legacy_mode disabled' do + it { is_expected.to eq(:online) } + end end context 'contacted 1s ago' do @@ -655,13 +782,29 @@ RSpec.describe Ci::Runner do it { is_expected.to eq(:online) } end - context 'contacted long time ago' do + context 'contacted recently' do before do - runner.contacted_at = 1.year.ago + runner.contacted_at = (3.months - 1.hour).ago end it { is_expected.to eq(:offline) } end + + context 'contacted long time ago' do + before do + runner.contacted_at = (3.months + 1.second).ago + end + + context 'with legacy_mode enabled' do + let(:legacy_mode) { '14.5' } + + it { is_expected.to eq(:offline) } + end + + context 'with legacy_mode disabled' do + it { is_expected.to eq(:stale) } + end + end end describe '#deprecated_rest_status' do @@ -760,8 +903,9 @@ RSpec.describe Ci::Runner do describe '#heartbeat' do let(:runner) { create(:ci_runner, :project) } + let(:executor) { 'shell' } - subject { runner.heartbeat(architecture: '18-bit', config: { gpus: "all" }) } + subject { runner.heartbeat(architecture: '18-bit', config: { gpus: "all" }, executor: executor) } context 'when database was updated recently' do before do @@ -797,6 +941,26 @@ RSpec.describe Ci::Runner do expect_redis_update does_db_update end + + %w(custom shell docker docker-windows docker-ssh ssh parallels virtualbox docker+machine docker-ssh+machine kubernetes some-unknown-type).each do |executor| + context "with #{executor} executor" do + let(:executor) { executor } + + it 'updates with expected executor type' do + expect_redis_update + + subject + + expect(runner.reload.read_attribute(:executor_type)).to eq(expected_executor_type) + end + + def expected_executor_type + return 'unknown' if executor == 'some-unknown-type' + + executor.gsub(/[+-]/, '_') + end + end + end end def expect_redis_update @@ -810,6 +974,7 @@ RSpec.describe Ci::Runner do expect { subject }.to change { runner.reload.read_attribute(:contacted_at) } .and change { runner.reload.read_attribute(:architecture) } .and change { runner.reload.read_attribute(:config) } + .and change { runner.reload.read_attribute(:executor_type) } end end @@ -1194,31 +1359,43 @@ RSpec.describe Ci::Runner do end describe '.belonging_to_group' do - it 'returns the specific group runner' do - group = create(:group) - runner = create(:ci_runner, :group, groups: [group]) - unrelated_group = create(:group) - create(:ci_runner, :group, groups: [unrelated_group]) + shared_examples 'returns group runners' do + it 'returns the specific group runner' do + group = create(:group) + runner = create(:ci_runner, :group, groups: [group]) + unrelated_group = create(:group) + create(:ci_runner, :group, groups: [unrelated_group]) - expect(described_class.belonging_to_group(group.id)).to contain_exactly(runner) - end + expect(described_class.belonging_to_group(group.id)).to contain_exactly(runner) + end - context 'runner belonging to parent group' do - let_it_be(:parent_group) { create(:group) } - let_it_be(:parent_runner) { create(:ci_runner, :group, groups: [parent_group]) } - let_it_be(:group) { create(:group, parent: parent_group) } + context 'runner belonging to parent group' do + let_it_be(:parent_group) { create(:group) } + let_it_be(:parent_runner) { create(:ci_runner, :group, groups: [parent_group]) } + let_it_be(:group) { create(:group, parent: parent_group) } - context 'when include_parent option is passed' do - it 'returns the group runner from the parent group' do - expect(described_class.belonging_to_group(group.id, include_ancestors: true)).to contain_exactly(parent_runner) + context 'when include_parent option is passed' do + it 'returns the group runner from the parent group' do + expect(described_class.belonging_to_group(group.id, include_ancestors: true)).to contain_exactly(parent_runner) + end end - end - context 'when include_parent option is not passed' do - it 'does not return the group runner from the parent group' do - expect(described_class.belonging_to_group(group.id)).to be_empty + context 'when include_parent option is not passed' do + it 'does not return the group runner from the parent group' do + expect(described_class.belonging_to_group(group.id)).to be_empty + end end end end + + it_behaves_like 'returns group runners' + + context 'when feature flag :linear_runner_ancestor_scopes is disabled' do + before do + stub_feature_flags(linear_runner_ancestor_scopes: false) + end + + it_behaves_like 'returns group runners' + end end end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 5e0fcb4882f..2b6f22e68f1 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -28,6 +28,18 @@ RSpec.describe Ci::Stage, :models do end end + describe '.by_position' do + it 'finds stages by position' do + a = create(:ci_stage_entity, position: 1) + b = create(:ci_stage_entity, position: 2) + c = create(:ci_stage_entity, position: 3) + + expect(described_class.by_position(1)).to contain_exactly(a) + expect(described_class.by_position(2)).to contain_exactly(b) + expect(described_class.by_position(%w[1 3])).to contain_exactly(a, c) + end + end + describe '.by_name' do it 'finds stages by name' do a = create(:ci_stage_entity, name: 'a') |