diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 21:18:33 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 21:18:33 +0300 |
commit | f64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch) | |
tree | a2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /spec/lib/gitlab/database | |
parent | bfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff) |
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'spec/lib/gitlab/database')
10 files changed, 708 insertions, 35 deletions
diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb new file mode 100644 index 00000000000..1020aafcf08 --- /dev/null +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model do + it_behaves_like 'having unique enum values' + + describe 'associations' do + it { is_expected.to belong_to(:batched_migration).with_foreign_key(:batched_background_migration_id) } + end + + describe 'delegated batched_migration attributes' do + let(:batched_job) { build(:batched_background_migration_job) } + let(:batched_migration) { batched_job.batched_migration } + + describe '#migration_aborted?' do + before do + batched_migration.status = :aborted + end + + it 'returns the migration aborted?' do + expect(batched_job.migration_aborted?).to eq(batched_migration.aborted?) + end + end + + describe '#migration_job_class' do + it 'returns the migration job_class' do + expect(batched_job.migration_job_class).to eq(batched_migration.job_class) + end + end + + describe '#migration_table_name' do + it 'returns the migration table_name' do + expect(batched_job.migration_table_name).to eq(batched_migration.table_name) + end + end + + describe '#migration_column_name' do + it 'returns the migration column_name' do + expect(batched_job.migration_column_name).to eq(batched_migration.column_name) + end + end + + describe '#migration_job_arguments' do + it 'returns the migration job_arguments' do + expect(batched_job.migration_job_arguments).to eq(batched_migration.job_arguments) + end + end + end +end diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb new file mode 100644 index 00000000000..f4a939e7c1f --- /dev/null +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :model do + it_behaves_like 'having unique enum values' + + describe 'associations' do + it { is_expected.to have_many(:batched_jobs).with_foreign_key(:batched_background_migration_id) } + + describe '#last_job' do + let!(:batched_migration) { create(:batched_background_migration) } + let!(:batched_job1) { create(:batched_background_migration_job, batched_migration: batched_migration) } + let!(:batched_job2) { create(:batched_background_migration_job, batched_migration: batched_migration) } + + it 'returns the most recent (in order of id) batched job' do + expect(batched_migration.last_job).to eq(batched_job2) + end + end + end + + describe '.queue_order' do + let!(:migration1) { create(:batched_background_migration) } + let!(:migration2) { create(:batched_background_migration) } + let!(:migration3) { create(:batched_background_migration) } + + it 'returns batched migrations ordered by their id' do + expect(described_class.queue_order.all).to eq([migration1, migration2, migration3]) + end + end + + describe '#interval_elapsed?' do + context 'when the migration has no last_job' do + let(:batched_migration) { build(:batched_background_migration) } + + it 'returns true' do + expect(batched_migration.interval_elapsed?).to eq(true) + end + end + + context 'when the migration has a last_job' do + let(:interval) { 2.minutes } + let(:batched_migration) { create(:batched_background_migration, interval: interval) } + + context 'when the last_job is less than an interval old' do + it 'returns false' do + freeze_time do + create(:batched_background_migration_job, + batched_migration: batched_migration, + created_at: Time.current - 1.minute) + + expect(batched_migration.interval_elapsed?).to eq(false) + end + end + end + + context 'when the last_job is exactly an interval old' do + it 'returns true' do + freeze_time do + create(:batched_background_migration_job, + batched_migration: batched_migration, + created_at: Time.current - 2.minutes) + + expect(batched_migration.interval_elapsed?).to eq(true) + end + end + end + + context 'when the last_job is more than an interval old' do + it 'returns true' do + freeze_time do + create(:batched_background_migration_job, + batched_migration: batched_migration, + created_at: Time.current - 3.minutes) + + expect(batched_migration.interval_elapsed?).to eq(true) + end + end + end + end + end + + describe '#create_batched_job!' do + let(:batched_migration) { create(:batched_background_migration) } + + it 'creates a batched_job with the correct batch configuration' do + batched_job = batched_migration.create_batched_job!(1, 5) + + expect(batched_job).to have_attributes( + min_value: 1, + max_value: 5, + batch_size: batched_migration.batch_size, + sub_batch_size: batched_migration.sub_batch_size) + end + end + + describe '#next_min_value' do + let!(:batched_migration) { create(:batched_background_migration) } + + context 'when a previous job exists' do + let!(:batched_job) { create(:batched_background_migration_job, batched_migration: batched_migration) } + + it 'returns the next value after the previous maximum' do + expect(batched_migration.next_min_value).to eq(batched_job.max_value + 1) + end + end + + context 'when a previous job does not exist' do + it 'returns the migration minimum value' do + expect(batched_migration.next_min_value).to eq(batched_migration.min_value) + end + end + end + + describe '#job_class' do + let(:job_class) { Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob } + let(:batched_migration) { build(:batched_background_migration) } + + it 'returns the class of the job for the migration' do + expect(batched_migration.job_class).to eq(job_class) + end + end + + describe '#batch_class' do + let(:batch_class) { Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy} + let(:batched_migration) { build(:batched_background_migration) } + + it 'returns the class of the batch strategy for the migration' do + expect(batched_migration.batch_class).to eq(batch_class) + end + end + + shared_examples_for 'an attr_writer that demodulizes assigned class names' do |attribute_name| + let(:batched_migration) { build(:batched_background_migration) } + + context 'when a module name exists' do + it 'removes the module name' do + batched_migration.public_send(:"#{attribute_name}=", '::Foo::Bar') + + expect(batched_migration[attribute_name]).to eq('Bar') + end + end + + context 'when a module name does not exist' do + it 'does not change the given class name' do + batched_migration.public_send(:"#{attribute_name}=", 'Bar') + + expect(batched_migration[attribute_name]).to eq('Bar') + end + end + end + + describe '#job_class_name=' do + it_behaves_like 'an attr_writer that demodulizes assigned class names', :job_class_name + end + + describe '#batch_class_name=' do + it_behaves_like 'an attr_writer that demodulizes assigned class names', :batch_class_name + end +end diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb new file mode 100644 index 00000000000..17cceb35ff7 --- /dev/null +++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '#perform' do + let(:migration_wrapper) { described_class.new } + let(:job_class) { Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob } + + let_it_be(:active_migration) { create(:batched_background_migration, :active, job_arguments: [:id, :other_id]) } + + let!(:job_record) { create(:batched_background_migration_job, batched_migration: active_migration) } + + it 'runs the migration job' do + expect_next_instance_of(job_class) do |job_instance| + expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id') + end + + migration_wrapper.perform(job_record) + end + + it 'updates the the tracking record in the database' do + expect(job_record).to receive(:update!).with(hash_including(attempts: 1, status: :running)).and_call_original + + freeze_time do + migration_wrapper.perform(job_record) + + reloaded_job_record = job_record.reload + + expect(reloaded_job_record).not_to be_pending + expect(reloaded_job_record.attempts).to eq(1) + expect(reloaded_job_record.started_at).to eq(Time.current) + end + end + + context 'when the migration job does not raise an error' do + it 'marks the tracking record as succeeded' do + expect_next_instance_of(job_class) do |job_instance| + expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id') + end + + freeze_time do + migration_wrapper.perform(job_record) + + reloaded_job_record = job_record.reload + + expect(reloaded_job_record).to be_succeeded + expect(reloaded_job_record.finished_at).to eq(Time.current) + end + end + end + + context 'when the migration job raises an error' do + it 'marks the tracking record as failed before raising the error' do + expect_next_instance_of(job_class) do |job_instance| + expect(job_instance).to receive(:perform) + .with(1, 10, 'events', 'id', 1, 'id', 'other_id') + .and_raise(RuntimeError, 'Something broke!') + end + + freeze_time do + expect { migration_wrapper.perform(job_record) }.to raise_error(RuntimeError, 'Something broke!') + + reloaded_job_record = job_record.reload + + expect(reloaded_job_record).to be_failed + expect(reloaded_job_record.finished_at).to eq(Time.current) + end + end + end +end diff --git a/spec/lib/gitlab/database/background_migration/scheduler_spec.rb b/spec/lib/gitlab/database/background_migration/scheduler_spec.rb new file mode 100644 index 00000000000..ba745acdf8a --- /dev/null +++ b/spec/lib/gitlab/database/background_migration/scheduler_spec.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::BackgroundMigration::Scheduler, '#perform' do + let(:scheduler) { described_class.new } + + shared_examples_for 'it has no jobs to run' do + it 'does not create and run a migration job' do + test_wrapper = double('test wrapper') + + expect(test_wrapper).not_to receive(:perform) + + expect do + scheduler.perform(migration_wrapper: test_wrapper) + end.not_to change { Gitlab::Database::BackgroundMigration::BatchedJob.count } + end + end + + context 'when there are no active migrations' do + let!(:migration) { create(:batched_background_migration, :finished) } + + it_behaves_like 'it has no jobs to run' + end + + shared_examples_for 'it has completed the migration' do + it 'marks the migration as finished' do + relation = Gitlab::Database::BackgroundMigration::BatchedMigration.finished.where(id: first_migration.id) + + expect { scheduler.perform }.to change { relation.count }.by(1) + end + end + + context 'when there are active migrations' do + let!(:first_migration) { create(:batched_background_migration, :active, batch_size: 2) } + let!(:last_migration) { create(:batched_background_migration, :active) } + + let(:job_relation) do + Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: first_migration.id) + end + + context 'when the migration interval has not elapsed' do + before do + expect_next_found_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |migration| + expect(migration).to receive(:interval_elapsed?).and_return(false) + end + end + + it_behaves_like 'it has no jobs to run' + end + + context 'when the interval has elapsed' do + before do + expect_next_found_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |migration| + expect(migration).to receive(:interval_elapsed?).and_return(true) + end + end + + context 'when the first migration has no previous jobs' do + context 'when the migration has batches to process' do + let!(:event1) { create(:event) } + let!(:event2) { create(:event) } + let!(:event3) { create(:event) } + + it 'runs the job for the first batch' do + first_migration.update!(min_value: event1.id, max_value: event3.id) + + expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper| + expect(wrapper).to receive(:perform).and_wrap_original do |_, job_record| + expect(job_record).to eq(job_relation.first) + end + end + + expect { scheduler.perform }.to change { job_relation.count }.by(1) + + expect(job_relation.first).to have_attributes( + min_value: event1.id, + max_value: event2.id, + batch_size: first_migration.batch_size, + sub_batch_size: first_migration.sub_batch_size) + end + end + + context 'when the migration has no batches to process' do + it_behaves_like 'it has no jobs to run' + it_behaves_like 'it has completed the migration' + end + end + + context 'when the first migration has previous jobs' do + let!(:event1) { create(:event) } + let!(:event2) { create(:event) } + let!(:event3) { create(:event) } + + let!(:previous_job) do + create(:batched_background_migration_job, + batched_migration: first_migration, + min_value: event1.id, + max_value: event2.id, + batch_size: 2, + sub_batch_size: 1) + end + + context 'when the migration is ready to process another job' do + it 'runs the migration job for the next batch' do + first_migration.update!(min_value: event1.id, max_value: event3.id) + + expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper| + expect(wrapper).to receive(:perform).and_wrap_original do |_, job_record| + expect(job_record).to eq(job_relation.last) + end + end + + expect { scheduler.perform }.to change { job_relation.count }.by(1) + + expect(job_relation.last).to have_attributes( + min_value: event3.id, + max_value: event3.id, + batch_size: first_migration.batch_size, + sub_batch_size: first_migration.sub_batch_size) + end + end + + context 'when the migration has no batches remaining' do + let!(:final_job) do + create(:batched_background_migration_job, + batched_migration: first_migration, + min_value: event3.id, + max_value: event3.id, + batch_size: 2, + sub_batch_size: 1) + end + + it_behaves_like 'it has no jobs to run' + it_behaves_like 'it has completed the migration' + end + end + + context 'when the bounds of the next batch exceed the migration maximum value' do + let!(:events) { create_list(:event, 3) } + let(:event1) { events[0] } + let(:event2) { events[1] } + + context 'when the batch maximum exceeds the migration maximum' do + it 'clamps the batch maximum to the migration maximum' do + first_migration.update!(batch_size: 5, min_value: event1.id, max_value: event2.id) + + expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper| + expect(wrapper).to receive(:perform) + end + + expect { scheduler.perform }.to change { job_relation.count }.by(1) + + expect(job_relation.first).to have_attributes( + min_value: event1.id, + max_value: event2.id, + batch_size: first_migration.batch_size, + sub_batch_size: first_migration.sub_batch_size) + end + end + + context 'when the batch minimum exceeds the migration maximum' do + let!(:previous_job) do + create(:batched_background_migration_job, + batched_migration: first_migration, + min_value: event1.id, + max_value: event2.id, + batch_size: 5, + sub_batch_size: 1) + end + + before do + first_migration.update!(batch_size: 5, min_value: 1, max_value: event2.id) + end + + it_behaves_like 'it has no jobs to run' + it_behaves_like 'it has completed the migration' + end + end + end + end +end diff --git a/spec/lib/gitlab/database/bulk_update_spec.rb b/spec/lib/gitlab/database/bulk_update_spec.rb index f2a7d6e69d8..dbafada26ca 100644 --- a/spec/lib/gitlab/database/bulk_update_spec.rb +++ b/spec/lib/gitlab/database/bulk_update_spec.rb @@ -13,8 +13,8 @@ RSpec.describe Gitlab::Database::BulkUpdate do i_a, i_b = create_list(:issue, 2) { - i_a => { title: 'Issue a' }, - i_b => { title: 'Issue b' } + i_a => { title: 'Issue a' }, + i_b => { title: 'Issue b' } } end @@ -51,7 +51,7 @@ RSpec.describe Gitlab::Database::BulkUpdate do it 'is possible to update all objects in a single query' do users = create_list(:user, 3) - mapping = users.zip(%w(foo bar baz)).to_h do |u, name| + mapping = users.zip(%w[foo bar baz]).to_h do |u, name| [u, { username: name, admin: true }] end @@ -61,13 +61,13 @@ RSpec.describe Gitlab::Database::BulkUpdate do # We have optimistically updated the values expect(users).to all(be_admin) - expect(users.map(&:username)).to eq(%w(foo bar baz)) + expect(users.map(&:username)).to eq(%w[foo bar baz]) users.each(&:reset) # The values are correct on reset expect(users).to all(be_admin) - expect(users.map(&:username)).to eq(%w(foo bar baz)) + expect(users.map(&:username)).to eq(%w[foo bar baz]) end it 'is possible to update heterogeneous sets' do @@ -79,8 +79,8 @@ RSpec.describe Gitlab::Database::BulkUpdate do mapping = { mr_a => { title: 'MR a' }, - i_a => { title: 'Issue a' }, - i_b => { title: 'Issue b' } + i_a => { title: 'Issue a' }, + i_b => { title: 'Issue b' } } expect do @@ -99,8 +99,8 @@ RSpec.describe Gitlab::Database::BulkUpdate do i_a, i_b = create_list(:issue, 2) mapping = { - i_a => { title: 'Issue a' }, - i_b => { title: 'Issue b' } + i_a => { title: 'Issue a' }, + i_b => { title: 'Issue b' } } described_class.execute(%i[title], mapping) @@ -113,23 +113,19 @@ RSpec.describe Gitlab::Database::BulkUpdate do include_examples 'basic functionality' context 'when prepared statements are configured differently to the normal test environment' do - # rubocop: disable RSpec/LeakyConstantDeclaration - # This cop is disabled because you cannot call establish_connection on - # an anonymous class. - class ActiveRecordBasePreparedStatementsInverted < ActiveRecord::Base - def self.abstract_class? - true # So it gets its own connection + before do + klass = Class.new(ActiveRecord::Base) do + def self.abstract_class? + true # So it gets its own connection + end end - end - # rubocop: enable RSpec/LeakyConstantDeclaration - before_all do + stub_const('ActiveRecordBasePreparedStatementsInverted', klass) + c = ActiveRecord::Base.connection.instance_variable_get(:@config) inverted = c.merge(prepared_statements: !ActiveRecord::Base.connection.prepared_statements) ActiveRecordBasePreparedStatementsInverted.establish_connection(inverted) - end - before do allow(ActiveRecord::Base).to receive(:connection_specification_name) .and_return(ActiveRecordBasePreparedStatementsInverted.connection_specification_name) end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 6de7fc3a50e..9178707a3d0 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -180,6 +180,32 @@ RSpec.describe Gitlab::Database::MigrationHelpers do end end + context 'when with_lock_retries re-runs the block' do + it 'only creates constraint for unique definitions' do + expected_sql = <<~SQL + ALTER TABLE "#{table_name}"\nADD CONSTRAINT "check_cda6f69506" CHECK (char_length("name") <= 255) + SQL + + expect(model).to receive(:create_table).twice.and_call_original + + expect(model).to receive(:execute).with(expected_sql).and_raise(ActiveRecord::LockWaitTimeout) + expect(model).to receive(:execute).with(expected_sql).and_call_original + + model.create_table_with_constraints table_name do |t| + t.timestamps_with_timezone + t.integer :some_id, null: false + t.boolean :active, null: false, default: true + t.text :name + + t.text_limit :name, 255 + end + + expect_table_columns_to_match(column_attributes, table_name) + + expect_check_constraint(table_name, 'check_cda6f69506', 'char_length(name) <= 255') + end + end + context 'when constraints are given invalid names' do let(:expected_max_length) { described_class::MAX_IDENTIFIER_NAME_LENGTH } let(:expected_error_message) { "The maximum allowed constraint name is #{expected_max_length} characters" } @@ -1720,7 +1746,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do .with( 2.minutes, 'CopyColumnUsingBackgroundMigrationJob', - [event.id, event.id, :events, :id, :id, 'id_convert_to_bigint', 100] + [event.id, event.id, :events, :id, 100, :id, 'id_convert_to_bigint'] ) expect(Gitlab::BackgroundMigration) diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb index 3e8563376ce..e25e4af2e86 100644 --- a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do context 'with enough rows to bulk queue jobs more than once' do before do - stub_const('Gitlab::Database::Migrations::BackgroundMigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE', 1) + stub_const('Gitlab::Database::Migrations::BackgroundMigrationHelpers::JOB_BUFFER_SIZE', 1) end it 'queues jobs correctly' do @@ -262,6 +262,120 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do end end + describe '#queue_batched_background_migration' do + it 'creates the database record for the migration' do + expect do + model.queue_batched_background_migration( + 'MyJobClass', + :projects, + :id, + job_interval: 5.minutes, + batch_min_value: 5, + batch_max_value: 1000, + batch_class_name: 'MyBatchClass', + batch_size: 100, + sub_batch_size: 10) + end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1) + + expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to have_attributes( + job_class_name: 'MyJobClass', + table_name: 'projects', + column_name: 'id', + interval: 300, + min_value: 5, + max_value: 1000, + batch_class_name: 'MyBatchClass', + batch_size: 100, + sub_batch_size: 10, + job_arguments: %w[], + status: 'active') + end + + context 'when the job interval is lower than the minimum' do + let(:minimum_delay) { described_class::BATCH_MIN_DELAY } + + it 'sets the job interval to the minimum value' do + expect do + model.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: minimum_delay - 1.minute) + end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1) + + created_migration = Gitlab::Database::BackgroundMigration::BatchedMigration.last + + expect(created_migration.interval).to eq(minimum_delay) + end + end + + context 'when additional arguments are passed to the method' do + it 'saves the arguments on the database record' do + expect do + model.queue_batched_background_migration( + 'MyJobClass', + :projects, + :id, + 'my', + 'arguments', + job_interval: 5.minutes, + batch_max_value: 1000) + end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1) + + expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to have_attributes( + job_class_name: 'MyJobClass', + table_name: 'projects', + column_name: 'id', + interval: 300, + min_value: 1, + max_value: 1000, + job_arguments: %w[my arguments]) + end + end + + context 'when the max_value is not given' do + context 'when records exist in the database' do + let!(:event1) { create(:event) } + let!(:event2) { create(:event) } + let!(:event3) { create(:event) } + + it 'creates the record with the current max value' do + expect do + model.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes) + end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1) + + created_migration = Gitlab::Database::BackgroundMigration::BatchedMigration.last + + expect(created_migration.max_value).to eq(event3.id) + end + + it 'creates the record with an active status' do + expect do + model.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes) + end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1) + + expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to be_active + end + end + + context 'when the database is empty' do + it 'sets the max value to the min value' do + expect do + model.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes) + end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1) + + created_migration = Gitlab::Database::BackgroundMigration::BatchedMigration.last + + expect(created_migration.max_value).to eq(created_migration.min_value) + end + + it 'creates the record with a finished status' do + expect do + model.queue_batched_background_migration('MyJobClass', :projects, :id, job_interval: 5.minutes) + end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1) + + expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to be_finished + end + end + end + end + describe '#migrate_async' do it 'calls BackgroundMigrationWorker.perform_async' do expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world") diff --git a/spec/lib/gitlab/database/migrations/observers/query_statistics_spec.rb b/spec/lib/gitlab/database/migrations/observers/query_statistics_spec.rb new file mode 100644 index 00000000000..a3b03050b33 --- /dev/null +++ b/spec/lib/gitlab/database/migrations/observers/query_statistics_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Gitlab::Database::Migrations::Observers::QueryStatistics do + subject { described_class.new } + + let(:connection) { ActiveRecord::Base.connection } + + def mock_pgss(enabled: true) + if enabled + allow(subject).to receive(:function_exists?).with(:pg_stat_statements_reset).and_return(true) + allow(connection).to receive(:view_exists?).with(:pg_stat_statements).and_return(true) + else + allow(subject).to receive(:function_exists?).with(:pg_stat_statements_reset).and_return(false) + allow(connection).to receive(:view_exists?).with(:pg_stat_statements).and_return(false) + end + end + + describe '#before' do + context 'with pgss available' do + it 'resets pg_stat_statements' do + mock_pgss(enabled: true) + expect(connection).to receive(:execute).with('select pg_stat_statements_reset()').once + + subject.before + end + end + + context 'without pgss available' do + it 'executes nothing' do + mock_pgss(enabled: false) + expect(connection).not_to receive(:execute) + + subject.before + end + end + end + + describe '#record' do + let(:observation) { Gitlab::Database::Migrations::Observation.new } + let(:result) { double } + let(:pgss_query) do + <<~SQL + SELECT query, calls, total_time, max_time, mean_time, rows + FROM pg_stat_statements + ORDER BY total_time DESC + SQL + end + + context 'with pgss available' do + it 'fetches data from pg_stat_statements and stores on the observation' do + mock_pgss(enabled: true) + expect(connection).to receive(:execute).with(pgss_query).once.and_return(result) + + expect { subject.record(observation) }.to change { observation.query_statistics }.from(nil).to(result) + end + end + + context 'without pgss available' do + it 'executes nothing' do + mock_pgss(enabled: false) + expect(connection).not_to receive(:execute) + + expect { subject.record(observation) }.not_to change { observation.query_statistics } + end + end + end +end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb index 76b1be1e497..757da2d9092 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb @@ -81,7 +81,7 @@ RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, : end describe '#rename_path_for_routable' do - context 'for namespaces' do + context 'for personal namespaces' do let(:namespace) { create(:namespace, path: 'the-path') } it "renames namespaces called the-path" do @@ -119,13 +119,16 @@ RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, : expect(project.route.reload.path).to eq('the-path-but-not-really/the-project') end + end - context "the-path namespace -> subgroup -> the-path0 project" do + context 'for groups' do + context "the-path group -> subgroup -> the-path0 project" do it "updates the route of the project correctly" do - subgroup = create(:group, path: "subgroup", parent: namespace) + group = create(:group, path: 'the-path') + subgroup = create(:group, path: "subgroup", parent: group) project = create(:project, :repository, path: "the-path0", namespace: subgroup) - subject.rename_path_for_routable(migration_namespace(namespace)) + subject.rename_path_for_routable(migration_namespace(group)) expect(project.route.reload.path).to eq("the-path0/subgroup/the-path0") end @@ -158,23 +161,27 @@ RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, : end describe '#perform_rename' do - describe 'for namespaces' do - let(:namespace) { create(:namespace, path: 'the-path') } - + context 'for personal namespaces' do it 'renames the path' do + namespace = create(:namespace, path: 'the-path') + subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed') expect(namespace.reload.path).to eq('renamed') + expect(namespace.reload.route.path).to eq('renamed') end + end - it 'renames all the routes for the namespace' do - child = create(:group, path: 'child', parent: namespace) + context 'for groups' do + it 'renames all the routes for the group' do + group = create(:group, path: 'the-path') + child = create(:group, path: 'child', parent: group) project = create(:project, :repository, namespace: child, path: 'the-project') - other_one = create(:namespace, path: 'the-path-is-similar') + other_one = create(:group, path: 'the-path-is-similar') - subject.perform_rename(migration_namespace(namespace), 'the-path', 'renamed') + subject.perform_rename(migration_namespace(group), 'the-path', 'renamed') - expect(namespace.reload.route.path).to eq('renamed') + expect(group.reload.route.path).to eq('renamed') expect(child.reload.route.path).to eq('renamed/child') expect(project.reload.route.path).to eq('renamed/child/the-project') expect(other_one.reload.route.path).to eq('the-path-is-similar') diff --git a/spec/lib/gitlab/database/similarity_score_spec.rb b/spec/lib/gitlab/database/similarity_score_spec.rb index cf75e5a72d9..b7b66494390 100644 --- a/spec/lib/gitlab/database/similarity_score_spec.rb +++ b/spec/lib/gitlab/database/similarity_score_spec.rb @@ -71,7 +71,7 @@ RSpec.describe Gitlab::Database::SimilarityScore do let(:search) { 'xyz' } it 'results have 0 similarity score' do - expect(query_result.map { |row| row['similarity'] }).to all(eq(0)) + expect(query_result.map { |row| row['similarity'].to_f }).to all(eq(0)) end end end |