aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/migration_test.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases/migration_test.rb')
-rw-r--r--activerecord/test/cases/migration_test.rb97
1 files changed, 97 insertions, 0 deletions
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 10f1c7216f..741bec6017 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -522,6 +522,79 @@ class MigrationTest < ActiveRecord::TestCase
end
end
+ if ActiveRecord::Base.connection.supports_advisory_locks?
+ def test_migrator_generates_valid_lock_key
+ migration = Class.new(ActiveRecord::Migration).new
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+
+ lock_key = migrator.send(:generate_migrator_advisory_lock_key)
+
+ assert ActiveRecord::Base.connection.get_advisory_lock(lock_key),
+ "the Migrator should have generated a valid lock key, but it didn't"
+ assert ActiveRecord::Base.connection.release_advisory_lock(lock_key),
+ "the Migrator should have generated a valid lock key, but it didn't"
+ end
+
+ def test_generate_migrator_advisory_lock_key
+ # It is important we are consistent with how we generate this so that
+ # exclusive locking works across migrator versions
+ migration = Class.new(ActiveRecord::Migration).new
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+
+ lock_key = migrator.send(:generate_migrator_advisory_lock_key)
+
+ current_database = ActiveRecord::Base.connection.current_database
+ salt = ActiveRecord::Migrator::MIGRATOR_SALT
+ expected_key = Zlib.crc32(current_database) * salt
+
+ assert lock_key == expected_key, "expected lock key generated by the migrator to be #{expected_key}, but it was #{lock_key} instead"
+ assert lock_key.is_a?(Fixnum), "expected lock key to be a Fixnum, but it wasn't"
+ assert lock_key.bit_length <= 63, "lock key must be a signed integer of max 63 bits magnitude"
+ end
+
+ def test_migrator_one_up_with_unavailable_lock
+ assert_no_column Person, :last_name
+
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ lock_key = migrator.send(:generate_migrator_advisory_lock_key)
+
+ with_another_process_holding_lock(lock_key) do
+ assert_raise(ActiveRecord::ConcurrentMigrationError) { migrator.migrate }
+ end
+
+ assert_no_column Person, :last_name,
+ "without an advisory lock, the Migrator should not make any changes, but it did."
+ end
+
+ def test_migrator_one_up_with_unavailable_lock_using_run
+ assert_no_column Person, :last_name
+
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+ lock_key = migrator.send(:generate_migrator_advisory_lock_key)
+
+ with_another_process_holding_lock(lock_key) do
+ assert_raise(ActiveRecord::ConcurrentMigrationError) { migrator.run }
+ end
+
+ assert_no_column Person, :last_name,
+ "without an advisory lock, the Migrator should not make any changes, but it did."
+ end
+ end
+
protected
# This is needed to isolate class_attribute assignments like `table_name_prefix`
# for each test case.
@@ -531,6 +604,30 @@ class MigrationTest < ActiveRecord::TestCase
def self.base_class; self; end
}
end
+
+ def with_another_process_holding_lock(lock_key)
+ thread_lock = Concurrent::CountDownLatch.new
+ test_terminated = Concurrent::CountDownLatch.new
+
+ other_process = Thread.new do
+ begin
+ conn = ActiveRecord::Base.connection_pool.checkout
+ conn.get_advisory_lock(lock_key)
+ thread_lock.count_down
+ test_terminated.wait # hold the lock open until we tested everything
+ ensure
+ conn.release_advisory_lock(lock_key)
+ ActiveRecord::Base.connection_pool.checkin(conn)
+ end
+ end
+
+ thread_lock.wait # wait until the 'other process' has the lock
+
+ yield
+
+ test_terminated.count_down
+ other_process.join
+ end
end
class ReservedWordsMigrationTest < ActiveRecord::TestCase