テストスイートの安定化と接続プールの堅牢化
Rails 8.1 の安定版ブランチに、テストのフレーキを抑えるための多数の修正がバックポートされました。環境変数のスコープ管理、接続プール内部の安全性向上、時間依存テストの決定論的化など、テスト実行環境の一貫性と信頼性が大幅に改善されています。
背景
テストスイートは 環境変数 の共有や接続プールのリーク、タイミング依存のフレーキによって CI での不安定さが報告されていたため、これらを根本的に解消する必要がありました。特に ActionMailbox 系のテストでキーが固定されていたことや、ConnectionPool の内部実装が ObjectSpace::WeakKeyMap の不具合に依存していた点が問題でした。
この PR はそれらの課題を解決するために、テストヘルパーと内部実装に対し スコープを明示的に管理する 改善を多数導入しています。結果として、テスト実行時の副作用が排除され、CI の再現性が向上しました。
技術的な変更
環境変数のスコープ管理:ActionMailbox のテストで ENV["MAILGUN_INGRESS_SIGNING_KEY"] と ENV["MANDRILL_INGRESS_API_KEY"] に対し setup で元値を保存し teardown で復元するコードが追加されました。また actionmailbox/test/test_helper.rb では RAILS_INBOUND_EMAIL_PASSWORD の同様の処理が実装されています。
setup do
@previous_key = ENV["MAILGUN_INGRESS_SIGNING_KEY"]
ENV["MAILGUN_INGRESS_SIGNING_KEY"] = "..."
ActionMailbox.ingress = :mailgun
end
teardown do
ENV["MAILGUN_INGRESS_SIGNING_KEY"] = @previous_key
end
接続プール内部の安全化:WeakThreadKeyMap が Concurrent::Map に置き換えられ、スレッドが死んだ際のエントリ除去ロジックが each_pair と delete に変更されました。さらに checkin と disconnect! の呼び出しを ActiveSupport.error_reporter.handle でラップし、例外レポートが確実に取得できるようになっています。
def initialize
@map = Concurrent::Map.new
end
def []=(key, value)
@map.each_pair do |thread, _|
@map.delete(thread) unless thread&.alive?
end
@map[key] = value
end
テストヘルパーの拡充:reset_connection が @connection.reset! を呼び出す形に変更され、reset_pool が追加されて接続プール全体を新しいアダプタに差し替える手段が提供されました。これによりテスト間でのプール状態のリセットが容易になり、リーク検出が正確になります。
def reset_connection
@connection.reset!
end
def reset_pool
original_connection = ActiveRecord::Base.remove_connection
ActiveRecord::Base.establish_connection(original_connection)
end
時間依存テストの決定論化:ConnectionPool#keepalive テストで実時間のスリープを排除し、Process.clock_gettime をスタブしてモノトニック時間を制御しています。これによりテストの実行時間が一定化され、フレーキが減少しました。
monotonic_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
Process.stub(:clock_gettime, ->(clock_id, *args) {
clock_id == Process::CLOCK_MONOTONIC ? monotonic_time : real_clock_gettime.call(clock_id, *args)
}) do
# テスト本体
end
その他の安定化:railties/test/application/console_test.rb にコンソールプロセスを明示的に kill するロジックが追加され、activesupport/test/evented_file_update_checker_test.rb では GC とスレッド join をループ化して確実にリソース解放を待機するよう修正されました。さらにフィクスチャデータの日付が固定値に置き換えられ、時間に依存しない比較が可能になっています。
設計判断
テストコードに限定した変更:今回の修正は本番ロジックに影響を与えないよう、テストディレクトリ配下とテストヘルパーのみを対象にしています。これによりリリース時のリグレッションリスクを最小化しつつ、開発者が CI で安定した結果を得られる環境を提供します。
後方互換性の保持:WeakThreadKeyMap の実装変更は内部実装の差し替えであり、外部 API はそのままです。また reset_connection の振る舞いは既存テストでも同様に動作し、reset_pool は新たに提供されたオプション機能であるため、既存ユーザーへの破壊的変更はありません。設計は「最小侵入」であり、テストインフラの改良に留められています。
明示的リソース管理の重視:環境変数の保存/復元、プールの明示的クリア、プロセスの kill など、テスト実行後の副作用を徹底的に排除する方針が取られました。これは Rails が提供するテストフレームワーク全体の信頼性を高めるための重要な設計指針です。
まとめ
本 PR は 8.1 向けに多数のテストスイート修正をバックポートし、環境変数管理から接続プール内部の安全化、時間依存テストの決定論化まで包括的にカバーしました。テストの隔離性と再現性が向上したことで、CI の安定運用が実現され、将来的な機能追加やバグ修正時の検証コストが低減します。