Skip to content

Commit

Permalink
Sync test DB from schema using its SHA1
Browse files Browse the repository at this point in the history
Previously, we used the migration status to determine whether the test
database(s) needed to be reloaded from the schema. This worked in most
cases, but if a schema.rb was modified outside of migrations or if a
migration was rolled back, it would require a manual db:test:prepare.

This commit updates load_schema to record the SHA1 of the loaded schema
file inside of the ar_internal_metadata table. We can then use this SHA
to determine whether we should reload the schema.

This ensures that the test DB stays exactly in sync with the schema
file, including rollbacks which fixes a test marked TODO.
  • Loading branch information
jhawthorn committed Aug 6, 2019
1 parent eb4faa1 commit ba093a5
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 13 deletions.
14 changes: 9 additions & 5 deletions activerecord/lib/active_record/migration.rb
Expand Up @@ -587,18 +587,22 @@ def check_pending!(connection = Base.connection)
end

def load_schema_if_pending!
if Base.connection.migration_context.needs_migration? || !Base.connection.migration_context.any_migrations?
current_config = Base.connection_config
all_configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)

unless all_configs.all? { |config| Tasks::DatabaseTasks.schema_up_to_date?(config.config) }
# Roundtrip to Rake to allow plugins to hook into database initialization.
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
FileUtils.cd(root) do
current_config = Base.connection_config
Base.clear_all_connections!
system("bin/rails db:test:prepare")
# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
Base.establish_connection(current_config)
end
check_pending!
end

# Establish a new connection, the old database may be gone (db:test:prepare uses purge)
Base.establish_connection(current_config)

check_pending!
end

def maintain_test_schema! #:nodoc:
Expand Down
15 changes: 15 additions & 0 deletions activerecord/lib/active_record/tasks/database_tasks.rb
Expand Up @@ -332,10 +332,21 @@ def load_schema(configuration, format = ActiveRecord::Base.schema_format, file =
end
ActiveRecord::InternalMetadata.create_table
ActiveRecord::InternalMetadata[:environment] = environment
ActiveRecord::InternalMetadata[:schema_sha1] = schema_sha1(file)
ensure
Migration.verbose = verbose_was
end

def schema_up_to_date?(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env, spec_name = "primary")
file ||= dump_filename(spec_name, format)

return true unless File.exist?(file)

ActiveRecord::Base.establish_connection(configuration)
return false unless ActiveRecord::InternalMetadata.table_exists?
ActiveRecord::InternalMetadata[:schema_sha1] == schema_sha1(file)
end

def dump_schema(configuration, format = ActiveRecord::Base.schema_format, spec_name = "primary") # :nodoc:
require "active_record/schema_dumper"
filename = dump_filename(spec_name, format)
Expand Down Expand Up @@ -467,6 +478,10 @@ def each_local_configuration
def local_database?(configuration)
configuration["host"].blank? || LOCAL_HOSTS.include?(configuration["host"])
end

def schema_sha1(file)
Digest::SHA1.hexdigest(File.read(file))
end
end
end
end
9 changes: 1 addition & 8 deletions railties/test/application/test_test.rb
Expand Up @@ -232,10 +232,7 @@ class UserTest < ActiveSupport::TestCase
assert_successful_test_run("models/user_test.rb")
end

# TODO: would be nice if we could detect the schema change automatically.
# For now, the user has to synchronize the schema manually.
# This test case serves as a reminder for this use case.
test "manually synchronize test schema after rollback" do
test "automatically synchronizes test schema after rollback" do
output = rails("generate", "model", "user", "name:string")
version = output.match(/(\d+)_create_users\.rb/)[1]

Expand Down Expand Up @@ -268,10 +265,6 @@ class UserTest < ActiveSupport::TestCase
end
RUBY

assert_successful_test_run "models/user_test.rb"

rails "db:test:prepare"

assert_unsuccessful_run "models/user_test.rb", <<-ASSERTION
Expected: ["id", "name"]
Actual: ["id", "name", "age"]
Expand Down

0 comments on commit ba093a5

Please sign in to comment.