テストヘルパーにDBクリーンアップブロックを追加し、per-PIDデータベースの残留を防止
use_postgresql / use_mysql2 がブロックを受け取れるようになり、テスト実行後に生成された railties_${Process.pid}_% パターンのデータベースを自動的に削除するようになりました。これにより、並列テストが作成するper-PIDデータベースがテスト環境に残留し続ける問題が解消されます。
背景
Railsのインテグレーションテストでは、ActiveRecord::TestDatabases.create_and_load_schema を通じてper-PIDおよびper-workerのデータベースが動的に生成されます。しかし rails db:drop は config/database.yml に定義されたデータベースしか削除しないため、railties_${Process.pid}_% という命名パターンで生成されたデータベースはテスト終了後も残留していました。
この問題に対応するため、各テストは ensure 節で個別に rails "db:drop" を呼び出していましたが、この方法では動的に生成されたデータベースは対象外でした。PR本文では、199ケースのテスト実行後に psql -d postgres -At -c 'SELECT datname FROM pg_database' のスナップショットを比較する形で検証が行われ、残留データベースがゼロになることが確認されています。なお、兄弟PR #57457 は同テストで発生する app_development / app_test データベースの残留を別途修正しています。
技術的な変更
中心となる変更は railties/test/isolation/abstract_unit.rb への with_test_database_cleanup ヘルパーの追加です。このヘルパーがブロック実行前後のデータベース一覧を差分比較し、ブロック内で新規作成されたデータベースを ensure で削除します。
追加された3つのメソッドがそれぞれ役割を分担しています:
def with_test_database_cleanup(adapter)
pre_existing_databases = list_test_databases(adapter) rescue nil
yield
ensure
if pre_existing_databases
drop_test_databases(adapter, list_test_databases(adapter) - pre_existing_databases)
end
end
def list_test_databases(adapter)
saved_db_config = ActiveRecord::Base.connection_pool.db_config rescue nil
ActiveRecord::Base.establish_connection(database_maintenance_config_for(adapter))
ActiveRecord::Base.lease_connection.select_values(database_list_sql_for(adapter))
ensure
ActiveRecord::Base.remove_connection
ActiveRecord::Base.establish_connection(saved_db_config) if saved_db_config
end
def drop_test_databases(adapter, databases)
saved_db_config = ActiveRecord::Base.connection_pool.db_config rescue nil
config = database_maintenance_config_for(adapter)
quietly do
databases.each do |db|
ActiveRecord::Tasks::DatabaseTasks.drop(config.merge(database: db))
end
end
ensure
# ...
end
list_test_databases は実行前後の接続状態を saved_db_config で退避・復元するため、テスト自体のDB接続に影響を与えません。drop_test_databases は ActiveRecord::Tasks::DatabaseTasks.drop を利用しており、rails db:drop コマンドではなくActiveRecordのタスクAPIを直接呼び出すことで、config/database.yml の定義に縛られずに任意のデータベースを削除できます。
既存の呼び出し箇所はブロック構文に移行されています。たとえば test_runner_test.rb の変更は典型的なパターンです:
変更前:
use_mysql2
exercise_parallelization_regardless_of_machine_core_count(with: :processes)
rails "generate", "scaffold", "User", "name:string"
# ...
変更後:
use_mysql2 do
exercise_parallelization_regardless_of_machine_core_count(with: :processes)
rails "generate", "scaffold", "User", "name:string"
# ...
end
各テストに散在していた ensure rails "db:drop" rescue nil 節は削除され、クリーンアップロジックがヘルパーに集約されました。また runner_test.rb では use_postgresql がブロック引数として db_name を渡すようになっており、ブロック外の変数への依存も解消されています。
設計判断
スナップショット差分方式(「ブロック実行前のDB一覧」と「実行後のDB一覧」の差分を削除)が採用されました。これにより、ヘルパー自身がどのデータベースが生成されたかを追跡する必要がなく、テスト内部の実装詳細に依存せずクリーンアップを実現できます。
list_test_databases の呼び出しが rescue nil でガードされている点も注目に値します。メンテナンス用接続の確立に失敗した場合(例えばDBサーバーが起動していないCI環境など)、クリーンアップを試みず pre_existing_databases を nil のままにすることで、ensure 節でのクリーンアップ処理をスキップします。これにより、クリーンアップ自体の失敗がテスト結果に影響しない設計になっています。
Rails::Command::DevcontainerTest の use_mysql2 呼び出しは config/database.yml の書き込みのみでデータベースを実際には作成しないため、今回のブロック化の対象外となっています。
まとめ
この変更は、テストヘルパーにスナップショット差分ベースのクリーンアップ機構を導入することで、config/database.yml の管理外にある動的生成データベースの残留問題を根本から解決しています。クリーンアップロジックの一元化と rescue nil によるフェイルセーフな設計は、テストインフラの堅牢性向上と各テストコードの簡素化を同時に実現しています。