aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/migrator_test.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases/migrator_test.rb')
-rw-r--r--activerecord/test/cases/migrator_test.rb508
1 files changed, 508 insertions, 0 deletions
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
new file mode 100644
index 0000000000..30e199f1c5
--- /dev/null
+++ b/activerecord/test/cases/migrator_test.rb
@@ -0,0 +1,508 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "cases/migration/helper"
+
+class MigratorTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ # Use this class to sense if migrations have gone
+ # up or down.
+ class Sensor < ActiveRecord::Migration::Current
+ attr_reader :went_up, :went_down
+
+ def initialize(name = self.class.name, version = nil)
+ super
+ @went_up = false
+ @went_down = false
+ end
+
+ def up; @went_up = true; end
+ def down; @went_down = true; end
+ end
+
+ def setup
+ super
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.message_count = 0
+ ActiveRecord::Migration.class_eval do
+ undef :puts
+ def puts(*)
+ ActiveRecord::Migration.message_count += 1
+ end
+ end
+ end
+
+ teardown do
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::Migration.class_eval do
+ undef :puts
+ def puts(*)
+ super
+ end
+ end
+ end
+
+ def test_migrator_with_duplicate_names
+ e = assert_raises(ActiveRecord::DuplicateMigrationNameError) do
+ list = [ActiveRecord::Migration.new("Chunky"), ActiveRecord::Migration.new("Chunky")]
+ ActiveRecord::Migrator.new(:up, list)
+ end
+ assert_match(/Multiple migrations have the name Chunky/, e.message)
+ end
+
+ def test_migrator_with_duplicate_versions
+ assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 1)]
+ ActiveRecord::Migrator.new(:up, list)
+ end
+ end
+
+ def test_migrator_with_missing_version_numbers
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, 3).run
+ end
+
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, -1).run
+ end
+
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, 0).run
+ end
+
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, 3).migrate
+ end
+
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
+ ActiveRecord::Migrator.new(:up, list, -1).migrate
+ end
+ end
+
+ def test_finds_migrations
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid").migrations
+
+ [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
+ assert_equal migrations[i].version, pair.first
+ assert_equal migrations[i].name, pair.last
+ end
+ end
+
+ def test_finds_migrations_in_subdirectories
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations
+
+ [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
+ assert_equal migrations[i].version, pair.first
+ assert_equal migrations[i].name, pair.last
+ end
+ end
+
+ def test_finds_migrations_from_two_directories
+ directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
+ migrations = ActiveRecord::MigrationContext.new(directories).migrations
+
+ [[20090101010101, "PeopleHaveHobbies"],
+ [20090101010202, "PeopleHaveDescriptions"],
+ [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"],
+ [20100201010101, "ValidWithTimestampsWeNeedReminders"],
+ [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i|
+ assert_equal pair.first, migrations[i].version
+ assert_equal pair.last, migrations[i].name
+ end
+ end
+
+ def test_finds_migrations_in_numbered_directory
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban").migrations
+ assert_equal 9, migrations[0].version
+ assert_equal "AddExpressions", migrations[0].name
+ end
+
+ def test_relative_migrations
+ list = Dir.chdir(MIGRATIONS_ROOT) do
+ ActiveRecord::MigrationContext.new("valid").migrations
+ end
+
+ migration_proxy = list.find { |item|
+ item.name == "ValidPeopleHaveLastNames"
+ }
+ assert migration_proxy, "should find pending migration"
+ end
+
+ def test_finds_pending_migrations
+ ActiveRecord::SchemaMigration.create!(version: "1")
+ migration_list = [ActiveRecord::Migration.new("foo", 1), ActiveRecord::Migration.new("bar", 3)]
+ migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations
+
+ assert_equal 1, migrations.size
+ assert_equal migration_list.last, migrations.first
+ end
+
+ def test_migrations_status
+ path = MIGRATIONS_ROOT + "/valid"
+
+ ActiveRecord::SchemaMigration.create(version: 2)
+ ActiveRecord::SchemaMigration.create(version: 10)
+
+ assert_equal [
+ ["down", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["down", "003", "Innocent jointable"],
+ ["up", "010", "********** NO FILE **********"],
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
+ end
+
+ def test_migrations_status_in_subdirectories
+ path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
+
+ ActiveRecord::SchemaMigration.create(version: 2)
+ ActiveRecord::SchemaMigration.create(version: 10)
+
+ assert_equal [
+ ["down", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["down", "003", "Innocent jointable"],
+ ["up", "010", "********** NO FILE **********"],
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
+ end
+
+ def test_migrations_status_with_schema_define_in_subdirectories
+ path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
+ prev_paths = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = path
+
+ ActiveRecord::Schema.define(version: 3) do
+ end
+
+ assert_equal [
+ ["up", "001", "Valid people have last names"],
+ ["up", "002", "We need reminders"],
+ ["up", "003", "Innocent jointable"],
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
+ ensure
+ ActiveRecord::Migrator.migrations_paths = prev_paths
+ end
+
+ def test_migrations_status_from_two_directories
+ paths = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
+
+ ActiveRecord::SchemaMigration.create(version: "20100101010101")
+ ActiveRecord::SchemaMigration.create(version: "20160528010101")
+
+ assert_equal [
+ ["down", "20090101010101", "People have hobbies"],
+ ["down", "20090101010202", "People have descriptions"],
+ ["up", "20100101010101", "Valid with timestamps people have last names"],
+ ["down", "20100201010101", "Valid with timestamps we need reminders"],
+ ["down", "20100301010101", "Valid with timestamps innocent jointable"],
+ ["up", "20160528010101", "********** NO FILE **********"],
+ ], ActiveRecord::MigrationContext.new(paths).migrations_status
+ end
+
+ def test_migrator_interleaved_migrations
+ pass_one = [Sensor.new("One", 1)]
+
+ ActiveRecord::Migrator.new(:up, pass_one).migrate
+ assert pass_one.first.went_up
+ assert_not pass_one.first.went_down
+
+ pass_two = [Sensor.new("One", 1), Sensor.new("Three", 3)]
+ ActiveRecord::Migrator.new(:up, pass_two).migrate
+ assert_not pass_two[0].went_up
+ assert pass_two[1].went_up
+ assert pass_two.all? { |x| !x.went_down }
+
+ pass_three = [Sensor.new("One", 1),
+ Sensor.new("Two", 2),
+ Sensor.new("Three", 3)]
+
+ ActiveRecord::Migrator.new(:down, pass_three).migrate
+ assert pass_three[0].went_down
+ assert_not pass_three[1].went_down
+ assert pass_three[2].went_down
+ end
+
+ def test_up_calls_up
+ migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
+ migrator = ActiveRecord::Migrator.new(:up, migrations)
+ migrator.migrate
+ assert migrations.all?(&:went_up)
+ assert migrations.all? { |m| !m.went_down }
+ assert_equal 2, migrator.current_version
+ end
+
+ def test_down_calls_down
+ test_up_calls_up
+
+ migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
+ migrator = ActiveRecord::Migrator.new(:down, migrations)
+ migrator.migrate
+ assert migrations.all? { |m| !m.went_up }
+ assert migrations.all?(&:went_down)
+ assert_equal 0, migrator.current_version
+ end
+
+ def test_current_version
+ ActiveRecord::SchemaMigration.create!(version: "1000")
+ migrator = ActiveRecord::MigrationContext.new("db/migrate")
+ assert_equal 1000, migrator.current_version
+ end
+
+ def test_migrator_one_up
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:up, migrations, 2).migrate
+ assert_equal [[:up, 2]], calls
+ end
+
+ def test_migrator_one_down
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 1).migrate
+
+ assert_equal [[:down, 3], [:down, 2]], calls
+ end
+
+ def test_migrator_one_up_one_down
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [[:down, 1]], calls
+ end
+
+ def test_migrator_double_up
+ calls, migrations = sensors(3)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
+ assert_equal(0, migrator.current_version)
+
+ migrator.migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ migrator.migrate
+ assert_equal [], calls
+ end
+
+ def test_migrator_double_down
+ calls, migrations = sensors(3)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
+
+ assert_equal 0, migrator.current_version
+
+ migrator.run
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ migrator = ActiveRecord::Migrator.new(:down, migrations, 1)
+ migrator.run
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ migrator.run
+ assert_equal [], calls
+
+ assert_equal 0, migrator.current_version
+ end
+
+ def test_migrator_verbosity
+ _, migrations = sensors(3)
+
+ ActiveRecord::Migration.verbose = true
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_not_equal 0, ActiveRecord::Migration.message_count
+
+ ActiveRecord::Migration.message_count = 0
+
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_not_equal 0, ActiveRecord::Migration.message_count
+ end
+
+ def test_migrator_verbosity_off
+ _, migrations = sensors(3)
+
+ ActiveRecord::Migration.verbose = false
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal 0, ActiveRecord::Migration.message_count
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal 0, ActiveRecord::Migration.message_count
+ end
+
+ def test_target_version_zero_should_run_only_once
+ calls, migrations = sensors(3)
+
+ # migrate up to 1
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ # migrate down to 0
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ # migrate down to 0 again
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [], calls
+ end
+
+ def test_migrator_going_down_due_to_version_target
+ calls, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
+
+ migrator.up(1)
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ migrator.migrate(0)
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ migrator.migrate
+ assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
+ end
+
+ def test_migrator_output_when_running_multiple_migrations
+ _, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
+
+ result = migrator.migrate
+ assert_equal(3, result.count)
+
+ # Nothing migrated from duplicate run
+ result = migrator.migrate
+ assert_equal(0, result.count)
+
+ result = migrator.rollback
+ assert_equal(1, result.count)
+ end
+
+ def test_migrator_output_when_running_single_migration
+ _, migrator = migrator_class(1)
+ migrator = migrator.new("valid")
+
+ result = migrator.run(:up, 1)
+
+ assert_equal(1, result.version)
+ end
+
+ def test_migrator_rollback
+ _, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
+
+ migrator.migrate
+ assert_equal(3, migrator.current_version)
+
+ migrator.rollback
+ assert_equal(2, migrator.current_version)
+
+ migrator.rollback
+ assert_equal(1, migrator.current_version)
+
+ migrator.rollback
+ assert_equal(0, migrator.current_version)
+
+ migrator.rollback
+ assert_equal(0, migrator.current_version)
+ end
+
+ def test_migrator_db_has_no_schema_migrations_table
+ _, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
+
+ ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
+ assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations")
+ migrator.migrate(1)
+ assert ActiveRecord::Base.connection.table_exists?("schema_migrations")
+ end
+
+ def test_migrator_forward
+ _, migrator = migrator_class(3)
+ migrator = migrator.new("/valid")
+ migrator.migrate(1)
+ assert_equal(1, migrator.current_version)
+
+ migrator.forward(2)
+ assert_equal(3, migrator.current_version)
+
+ migrator.forward
+ assert_equal(3, migrator.current_version)
+ end
+
+ def test_only_loads_pending_migrations
+ # migrate up to 1
+ ActiveRecord::SchemaMigration.create!(version: "1")
+
+ calls, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
+ migrator.migrate
+
+ assert_equal [[:up, 2], [:up, 3]], calls
+ end
+
+ def test_get_all_versions
+ _, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
+
+ migrator.migrate
+ assert_equal([1, 2, 3], migrator.get_all_versions)
+
+ migrator.rollback
+ assert_equal([1, 2], migrator.get_all_versions)
+
+ migrator.rollback
+ assert_equal([1], migrator.get_all_versions)
+
+ migrator.rollback
+ assert_equal([], migrator.get_all_versions)
+ end
+
+ private
+ def m(name, version)
+ x = Sensor.new name, version
+ x.extend(Module.new {
+ define_method(:up) { yield(:up, x); super() }
+ define_method(:down) { yield(:down, x); super() }
+ }) if block_given?
+ end
+
+ def sensors(count)
+ calls = []
+ migrations = count.times.map { |i|
+ m(nil, i + 1) { |c, migration|
+ calls << [c, migration.version]
+ }
+ }
+ [calls, migrations]
+ end
+
+ def migrator_class(count)
+ calls, migrations = sensors(count)
+
+ migrator = Class.new(ActiveRecord::MigrationContext) {
+ define_method(:migrations) { |*|
+ migrations
+ }
+ }
+ [calls, migrator]
+ end
+end