古いJRuby(9.2未満)向けワークアラウンドの一掃
JRuby 9.2未満のEOLバージョンを対象に追加されていたワークアラウンドとハックを削除し、コードベースの保守性を高めました。#323でJRuby 10.1のサポートが追加されたことを受け、不要になった負債を整理しています。
背景
rspecのコードベースには、長年にわたりJRuby固有のバグへの対処コードが蓄積されていました。今回の整理が可能になったのは、対象となるJRubyバージョンがすべてEOLとなったためです。#323でJRuby 10.1(CRuby 4.0互換)のCI対応が完了し、現在サポートされるJRubyはすべて9.2以降となっています。
PRが参照するワークアラウンドの根拠となっていたJRubyのバグは以下のものです:
-
jruby/jruby#1398:
method_missingでのメソッド可視性エラー(9.2で解消) -
jruby/jruby#2767:
Open3.popen3がシェルエスケープ済み引数を正しく扱わない -
jruby/jruby#2816 / #2817:
UnboundMethod#parametersの部分的サポート - jruby/jruby#3386 / #4562 / #5209: Ripperの各種バグ
-
jruby/jruby#4467:
rescue節でバックトレースが取得できないバグ
これらのバグはいずれも修正済みであり、対象バージョンのEOLと合わせてワークアラウンドを維持する意義がなくなっていました。
技術的な変更
複数のライブラリにまたがり、バージョン条件分岐・エイリアス定義・代替実装が削除されました。
bisect/shell_command.rb: open3_safe_escapeの廃止
rspec-core のビセクト機能では、JRubyのOpen3.popen3がシェルエスケープ済み引数を正しく処理しないバグ(jruby/jruby#2767)に対処するため、open3_safe_escapeメソッドをJRubyではquoteに、それ以外ではescapeにエイリアスしていました。このバグが解消されたため、open3_safe_escapeを廃止し、すべての呼び出し箇所を直接escapeに置き換えています。
変更前:
if RSpec::Support::Ruby.jruby?
# :nocov:
alias open3_safe_escape quote
# :nocov:
else
alias open3_safe_escape escape
end
# ...
parts << open3_safe_escape(RSpec::Core.path_to_executable)
parts.concat(locations.map { |l| open3_safe_escape(l) })
@load_path ||= "-I#{$LOAD_PATH.map { |p| open3_safe_escape(p) }.join(':')}"
変更後:
parts << escape(RSpec::Core.path_to_executable)
parts.concat(locations.map { |l| escape(l) })
@load_path ||= "-I#{$LOAD_PATH.map { |p| escape(p) }.join(':')}"
テストコード側でも、uses_quoting_for_escaping?の条件からJRubyの判定が除かれ、RSpec::Support::OS.windows?のみの判定となりました。
example.rb: rescue節の=> _ハック削除
JRuby固有のバグ(jruby/jruby#4467)を回避するため、rescue Pending::SkipDeclaredInExample => _ と変数代入形式を使っていた箇所を、通常の rescue Pending::SkipDeclaredInExample に戻しています。
変更前:
rescue Pending::SkipDeclaredInExample => _
# The "=> _" is normally useless but on JRuby it is a workaround
# for a bug that prevents us from getting backtraces:
# https://github.com/jruby/jruby/issues/4467
#
# no-op, required metadata has already been set by the `skip`
# method.
変更後:
rescue Pending::SkipDeclaredInExample
# no-op, required metadata has already been set by the `skip`
# method.
failure_aggregator.rb: assign_backtraceの統一
rspec-expectations では、JRuby 9.1以前でcallerとraiseが生成するバックトレースのフレームが異なる問題に対し、バージョン条件付きで2種類のassign_backtrace実装を切り替えていました。9.2未満のサポート終了に伴い、パフォーマンス面で優れるシンプルなcallerベースの実装のみが残されています。
変更前:
if RSpec::Support::Ruby.jruby? && RSpec::Support::Ruby.jruby_version < '9.2.0.0'
# :nocov:
def assign_backtrace(failure)
raise failure
rescue failure.class => e
failure.set_backtrace(e.backtrace)
end
# :nocov:
else
def assign_backtrace(failure)
failure.set_backtrace(caller)
end
end
変更後:
# Using `caller` performs better (and is simpler) than `raise` on most Rubies.
def assign_backtrace(failure)
failure.set_backtrace(caller)
end
method_signature_verifier.rb: Javaプロキシメソッド向け回避策の削除
rspec-support では、JRubyのUnboundMethod#parametersが空を返すバグと、Javaプロキシメソッドのarityが常に-1を返すバグに対処するため、MethodSignatureクラスを実行時に動的に再定義するコードが存在していました。これらのバグの修正状況をJava::JavaLang::String.instance_method(:char_at).parameters等で検出してクラスを差し替えるアプローチは、44行のコードを要していましたが、今回すべて削除されています。
ruby_features_spec.rb: Ripper検証ロジックの削除
ripper_works_correctly?メソッドと、それを構成する3つのJRuby固有バグ検証メソッド(ripper_reports_correct_line_number?、ripper_can_parse_source_including_keywordish_symbol?、ripper_can_parse_source_referencing_keyword_arguments?)が削除されました。ripper_supported?の判定はripper_is_implemented?のみに簡略化されています。
その他の変更
-
spec_file_load_errors_spec.rb:defined?(JRUBY_VERSION) && !JRUBY_VERSION.empty?による手動JRuby判定をRSpec::Support::Ruby.jruby?に統一 -
library_wide_checks.rb:--disable=gemオプションをMRIのみに適用していた条件を削除し、全Ruby実装に適用 -
backtrace_exclusion_patterns.feature: バックトレース表示の正規表現パターンをJRuby 9.2以降のフォーマットに合わせて更新
設計判断
バージョン条件分岐の代わりに実装を1本化する方針が一貫して採られています。JRuby固有のパスは:nocov:タグで収支カバレッジから除外されていたものが多く、実際には動作確認が困難な状態になっていました。EOLバージョンのサポート終了というタイミングを活用し、こうした「テスト不能なコード」を一掃しています。
test_double.rbの変更は特徴的で、JRubyバグ回避として導入したraise_non_public_errorによる独自エラーが、結果的にCRubyと共通で有益な動作(エラーメッセージに追加コンテキストを含む)をもたらしていたため、実装自体は維持しつつコメントのみ更新されています。バグ回避が意図せず機能改善になっていたケースとして、コードの意図を正確に記録した変更です。
まとめ
本PRは、EOLとなったJRuby 9.2未満を対象としたワークアラウンドを取り除くことで、コードベースから「歴史的経緯によるノイズ」を削減した変更です。条件分岐・動的クラス再定義・エイリアス定義といった保守コストの高いパターンが消え、現行のJRuby(9.4以降)に対してもシンプルな実装パスを通るようになっています。