From 89b4733ff215c98b518e0f20beb8850ab1f1cecb Mon Sep 17 00:00:00 2001 From: Matijs van Zuijlen Date: Wed, 27 Dec 2023 16:01:52 +0100 Subject: [PATCH 1/5] Build with Rubies 3.0 through 3.3 in CI --- .github/workflows/ruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 54d9f48..2ddb633 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - ruby: ["3.0", "3.1", "3.2"] + ruby: ["3.0", "3.1", "3.2", "3.3"] steps: - uses: actions/checkout@v4 From 150389c7eebf9f2a9bed0048960307a0bd370059 Mon Sep 17 00:00:00 2001 From: Matijs van Zuijlen Date: Thu, 28 Dec 2023 09:06:39 +0100 Subject: [PATCH 2/5] Recognize eval location when running with Ruby 3.3 --- lib/live_ast/linker.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/live_ast/linker.rb b/lib/live_ast/linker.rb index e79c5c5..271efc7 100644 --- a/lib/live_ast/linker.rb +++ b/lib/live_ast/linker.rb @@ -74,6 +74,7 @@ def find_method_ast(klass, name, *location) def find_ast(*location) raise ASTNotFoundError unless location.size == 2 raise RawEvalError if location.first == "(eval)" + raise RawEvalError if location.first.start_with? "(eval at " # Ruby 3.3 raise ASTNotFoundError if location.first == "" ast = fetch_from_cache(*location) From ab136f6b7f20da002ea9940d8a961a6a3f3ba2a2 Mon Sep 17 00:00:00 2001 From: Matijs van Zuijlen Date: Thu, 28 Dec 2023 09:07:04 +0100 Subject: [PATCH 3/5] Update test to handle changed error backtrace in Ruby 3.3 --- test/caller_test.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/caller_test.rb b/test/caller_test.rb index a66448b..173713b 100644 --- a/test/caller_test.rb +++ b/test/caller_test.rb @@ -31,6 +31,9 @@ def raise_after_eval(code, will_succeed) foo RUBY + orig.shift if orig.first.start_with? " Date: Thu, 28 Dec 2023 09:37:58 +0100 Subject: [PATCH 4/5] Accept nil filename argument on Ruby 3.3 In Ruby 3.3, nil is accepted as a second argument to #instance_eval, so we must also accept it in the replacement method. --- lib/live_ast/common.rb | 2 ++ test/full/replace_eval_test.rb | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/live_ast/common.rb b/lib/live_ast/common.rb index 9cea0fd..c7249f9 100644 --- a/lib/live_ast/common.rb +++ b/lib/live_ast/common.rb @@ -14,6 +14,8 @@ def arg_to_str(arg) end def arg_to_str2(arg) + return "" if arg.nil? && RUBY_VERSION >= "3.3.0" + arg.to_str rescue NameError thing = arg&.class diff --git a/test/full/replace_eval_test.rb b/test/full/replace_eval_test.rb index 1127677..a16dfbd 100644 --- a/test/full/replace_eval_test.rb +++ b/test/full/replace_eval_test.rb @@ -173,6 +173,8 @@ def test_instance_eval_code_argument_type_error_no_block end def test_instance_eval_filename_argument_nil_type_error_no_block + skip "nil is an acceptable filename argument in Ruby 3.3" if RUBY_VERSION >= "3.3.0" + orig = assert_raises TypeError do Object.new.live_ast_original_instance_eval("1", nil) end @@ -184,6 +186,15 @@ def test_instance_eval_filename_argument_nil_type_error_no_block assert_equal orig.class, live.class end + def test_instance_eval_filename_argument_nil_ruby_3_3_no_block + unless RUBY_VERSION >= "3.3.0" + skip "nil is not an acceptable filename argument before Ruby 3.3" + end + + assert_equal 1, Object.new.live_ast_original_instance_eval("1", nil) + assert_equal 1, Object.new.instance_eval("1", nil) + end + def test_instance_eval_filename_argument_conversion_type_error_no_block orig = assert_raises TypeError do Object.new.live_ast_original_instance_eval("1", 23) @@ -360,6 +371,7 @@ def test_module_eval_file_line def test_module_eval_to_str file = Minitest::Mock.new file.expect(:to_str, "zebra.rb") + file.expect(:nil?, false) Class.new.module_eval("33 + 44", file) file.verify end From 531ef4eba04d57ab1f2a23b0a132be63a5c15474 Mon Sep 17 00:00:00 2001 From: Matijs van Zuijlen Date: Thu, 28 Dec 2023 15:22:37 +0100 Subject: [PATCH 5/5] Include file and line in the eval file name on Ruby 3.3 --- lib/live_ast/common.rb | 4 +++- lib/live_ast/linker.rb | 2 +- test/backtrace_test.rb | 9 +++++++- test/full/replace_eval_test.rb | 38 +++++++++++++++++++++------------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/lib/live_ast/common.rb b/lib/live_ast/common.rb index c7249f9..5807023 100644 --- a/lib/live_ast/common.rb +++ b/lib/live_ast/common.rb @@ -49,8 +49,10 @@ def location_for_eval(bind, filename = nil, lineno = nil) if filename lineno ||= 1 [filename, lineno] + elsif RUBY_VERSION >= "3.3.0" + file, line = bind.source_location + ["(eval at #{file}:#{line})", 1] else - bind.source_location ["(eval)", 1] end end diff --git a/lib/live_ast/linker.rb b/lib/live_ast/linker.rb index 271efc7..0869cc2 100644 --- a/lib/live_ast/linker.rb +++ b/lib/live_ast/linker.rb @@ -74,7 +74,7 @@ def find_method_ast(klass, name, *location) def find_ast(*location) raise ASTNotFoundError unless location.size == 2 raise RawEvalError if location.first == "(eval)" - raise RawEvalError if location.first.start_with? "(eval at " # Ruby 3.3 + raise RawEvalError if location.first.match?(/^\(eval at .*\)$/) # Ruby 3.3 raise ASTNotFoundError if location.first == "" ast = fetch_from_cache(*location) diff --git a/test/backtrace_test.rb b/test/backtrace_test.rb index 31ba3f5..71d8caa 100644 --- a/test/backtrace_test.rb +++ b/test/backtrace_test.rb @@ -51,7 +51,14 @@ def test_raise_no_overrides RUBY end - assert_equal orig.first, live.first + if RUBY_VERSION >= "3.3.0" + expected = orig.first.sub(/:[0-9]+\)/, ":LINE)") + actual = live.first.sub(/:[0-9]+\)/, ":LINE)") + + assert_equal expected, actual + else + assert_equal orig.first, live.first + end end end diff --git a/test/full/replace_eval_test.rb b/test/full/replace_eval_test.rb index a16dfbd..b8652ca 100644 --- a/test/full/replace_eval_test.rb +++ b/test/full/replace_eval_test.rb @@ -416,33 +416,43 @@ def test_local_var_collision end def test_eval_location_without_binding - expected = ["(eval)", 2] + expected_file = if RUBY_VERSION >= "3.3.0" + /^\(eval at #{__FILE__}:[0-9]+\)$/ + else + /^\(eval\)$/ + end - assert_equal expected, live_ast_original_eval("\n[__FILE__, __LINE__]") + file, line = live_ast_original_eval("\n[__FILE__, __LINE__]") - unfixable do - assert_equal expected, eval("\n[__FILE__, __LINE__]") - end + assert_match expected_file, file + assert_equal 2, line file, line = eval("\n[__FILE__, __LINE__]") - file = LiveAST.strip_token file + adjusted_file = LiveAST.strip_token file - assert_equal expected, [file, line] + refute_match expected_file, file + assert_match expected_file, adjusted_file + assert_equal 2, line end def test_eval_location_with_binding - expected = ["(eval)", 2] + expected_file = if RUBY_VERSION >= "3.3.0" + /^\(eval at #{__FILE__}:[0-9]+\)$/ + else + /^\(eval\)$/ + end - assert_equal expected, live_ast_original_eval("\n[__FILE__, __LINE__]", binding) + file, line = live_ast_original_eval("\n[__FILE__, __LINE__]", binding) - unfixable do - assert_equal expected, eval("\n[__FILE__, __LINE__]", binding) - end + assert_match expected_file, file + assert_equal 2, line file, line = eval("\n[__FILE__, __LINE__]", binding) - file = LiveAST.strip_token file + adjusted_file = LiveAST.strip_token file - assert_equal expected, [file, line] + refute_match expected_file, file + assert_match expected_file, adjusted_file + assert_equal 2, line end DEFINE_BO_TEST = lambda do