1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/test/cases/migrator_test.rb
eileencodes 7cc27d749c Move SchemaMigration to migration_context
This PR moves the `schema_migration` to `migration_context` so that we
can access the `schema_migration` per connection.

This does not change behavior of the SchemaMigration if you are using
one database. This also does not change behavior of any public APIs.
`Migrator` is private as is `MigrationContext` so we can change these as
needed.

We now need to pass a `schema_migration` to `Migrator` so that we can
run migrations on the right connection outside the context of a rake
task.

The bugs this fixes were discovered while debugging the issues around
the SchemaCache on initialization with multiple database. It was clear
that `get_all_versions` wouldn't work without these changes outside the
context of a rake task (because in the rake task we establish a
connection and change AR::Base.connection to the db we're running on).

Because the `SchemaCache` relies on the `SchemaMigration` information we
need to make sure we store it per-connection rather than on
ActiveRecord::Base.

[Eileen M. Uchitelle & Aaron Patterson]
2019-06-14 11:15:08 -04:00

527 lines
17 KiB
Ruby

# 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
@schema_migration = ActiveRecord::Base.connection.schema_migration
@schema_migration.create_table
@schema_migration.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
@schema_migration.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, @schema_migration)
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, @schema_migration)
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, @schema_migration, 3).run
end
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
ActiveRecord::Migrator.new(:up, list, @schema_migration, -1).run
end
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
ActiveRecord::Migrator.new(:up, list, @schema_migration, 0).run
end
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
ActiveRecord::Migrator.new(:up, list, @schema_migration, 3).migrate
end
assert_raises(ActiveRecord::UnknownMigrationVersionError) do
list = [ActiveRecord::Migration.new("Foo", 1), ActiveRecord::Migration.new("Bar", 2)]
ActiveRecord::Migrator.new(:up, list, @schema_migration, -1).migrate
end
end
def test_finds_migrations
schema_migration = ActiveRecord::Base.connection.schema_migration
migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid", schema_migration).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
schema_migration = ActiveRecord::Base.connection.schema_migration
migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories", schema_migration).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
schema_migration = ActiveRecord::Base.connection.schema_migration
directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
migrations = ActiveRecord::MigrationContext.new(directories, schema_migration).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
schema_migration = ActiveRecord::Base.connection.schema_migration
migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban", schema_migration).migrations
assert_equal 9, migrations[0].version
assert_equal "AddExpressions", migrations[0].name
end
def test_relative_migrations
schema_migration = ActiveRecord::Base.connection.schema_migration
list = Dir.chdir(MIGRATIONS_ROOT) do
ActiveRecord::MigrationContext.new("valid", schema_migration).migrations
end
migration_proxy = list.find { |item|
item.name == "ValidPeopleHaveLastNames"
}
assert migration_proxy, "should find pending migration"
end
def test_finds_pending_migrations
@schema_migration.create!(version: "1")
migration_list = [ActiveRecord::Migration.new("foo", 1), ActiveRecord::Migration.new("bar", 3)]
migrations = ActiveRecord::Migrator.new(:up, migration_list, @schema_migration).pending_migrations
assert_equal 1, migrations.size
assert_equal migration_list.last, migrations.first
end
def test_migrations_status
path = MIGRATIONS_ROOT + "/valid"
schema_migration = ActiveRecord::Base.connection.schema_migration
@schema_migration.create(version: 2)
@schema_migration.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, schema_migration).migrations_status
end
def test_migrations_status_in_subdirectories
path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
schema_migration = ActiveRecord::Base.connection.schema_migration
@schema_migration.create(version: 2)
@schema_migration.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, schema_migration).migrations_status
end
def test_migrations_status_with_schema_define_in_subdirectories
path = MIGRATIONS_ROOT + "/valid_with_subdirectories"
prev_paths = ActiveRecord::Migrator.migrations_paths
schema_migration = ActiveRecord::Base.connection.schema_migration
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, schema_migration).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"]
schema_migration = ActiveRecord::Base.connection.schema_migration
@schema_migration.create(version: "20100101010101")
@schema_migration.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, schema_migration).migrations_status
end
def test_migrator_interleaved_migrations
pass_one = [Sensor.new("One", 1)]
ActiveRecord::Migrator.new(:up, pass_one, @schema_migration).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, @schema_migration).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, @schema_migration).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, @schema_migration)
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, @schema_migration)
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
@schema_migration.create!(version: "1000")
schema_migration = ActiveRecord::Base.connection.schema_migration
migrator = ActiveRecord::MigrationContext.new("db/migrate", schema_migration)
assert_equal 1000, migrator.current_version
end
def test_migrator_one_up
calls, migrations = sensors(3)
ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 1).migrate
assert_equal [[:up, 1]], calls
calls.clear
ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 2).migrate
assert_equal [[:up, 2]], calls
end
def test_migrator_one_down
calls, migrations = sensors(3)
ActiveRecord::Migrator.new(:up, migrations, @schema_migration).migrate
assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
calls.clear
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 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, @schema_migration, 1).migrate
assert_equal [[:up, 1]], calls
calls.clear
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
assert_equal [[:down, 1]], calls
end
def test_migrator_double_up
calls, migrations = sensors(3)
migrator = ActiveRecord::Migrator.new(:up, migrations, @schema_migration, 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, @schema_migration, 1)
assert_equal 0, migrator.current_version
migrator.run
assert_equal [[:up, 1]], calls
calls.clear
migrator = ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 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, @schema_migration, 1).migrate
assert_not_equal 0, ActiveRecord::Migration.message_count
ActiveRecord::Migration.message_count = 0
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 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, @schema_migration, 1).migrate
assert_equal 0, ActiveRecord::Migration.message_count
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 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, @schema_migration, 1).migrate
assert_equal [[:up, 1]], calls
calls.clear
# migrate down to 0
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
assert_equal [[:down, 1]], calls
calls.clear
# migrate down to 0 again
ActiveRecord::Migrator.new(:down, migrations, @schema_migration, 0).migrate
assert_equal [], calls
end
def test_migrator_going_down_due_to_version_target
schema_migration = ActiveRecord::Base.connection.schema_migration
calls, migrator = migrator_class(3)
migrator = migrator.new("valid", schema_migration)
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
schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
migrator = migrator.new("valid", schema_migration)
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
schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(1)
migrator = migrator.new("valid", schema_migration)
result = migrator.run(:up, 1)
assert_equal(1, result.version)
end
def test_migrator_rollback
schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
migrator = migrator.new("valid", schema_migration)
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
schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
migrator = migrator.new("valid", schema_migration)
ActiveRecord::SchemaMigration.drop_table
assert_not_predicate ActiveRecord::SchemaMigration, :table_exists?
migrator.migrate(1)
assert_predicate ActiveRecord::SchemaMigration, :table_exists?
end
def test_migrator_forward
schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
migrator = migrator.new("/valid", schema_migration)
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
@schema_migration.create!(version: "1")
schema_migration = ActiveRecord::Base.connection.schema_migration
calls, migrator = migrator_class(3)
migrator = migrator.new("valid", schema_migration)
migrator.migrate
assert_equal [[:up, 2], [:up, 3]], calls
end
def test_get_all_versions
schema_migration = ActiveRecord::Base.connection.schema_migration
_, migrator = migrator_class(3)
migrator = migrator.new("valid", schema_migration)
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