[ruby/irb] Support `IRB.conf[:BACKTRACE_FILTER]`

(https://github.com/ruby/irb/pull/917)

* Use 'irbtest-' instead if 'irb-' as prefix of test files.

Otherwise IRB would mis-recognize exceptions raised in test files as
exceptions raised in IRB itself.

* Support `IRB.conf[:BACKTRACE_FILTER]``

This config allows users to customize the backtrace of exceptions raised
and displayed in IRB sessions. This is useful for filtering out library
frames from the backtrace.

IRB expects the given value to response to `call` method and return
the filtered backtrace.

https://github.com/ruby/irb/commit/6f6e87d769
This commit is contained in:
Stan Lo 2024-05-01 22:23:05 +08:00 коммит произвёл git
Родитель 2a978ee047
Коммит 1000c27db8
3 изменённых файлов: 115 добавлений и 18 удалений

Просмотреть файл

@ -1242,27 +1242,33 @@ module IRB
irb_bug = true
else
irb_bug = false
# This is mostly to make IRB work nicely with Rails console's backtrace filtering, which patches WorkSpace#filter_backtrace
# In such use case, we want to filter the exception's backtrace before its displayed through Exception#full_message
# And we clone the exception object in order to avoid mutating the original exception
# TODO: introduce better API to expose exception backtrace externally
backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact
# To support backtrace filtering while utilizing Exception#full_message, we need to clone
# the exception to avoid modifying the original exception's backtrace.
exc = exc.clone
exc.set_backtrace(backtrace)
filtered_backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact
backtrace_filter = IRB.conf[:BACKTRACE_FILTER]
if backtrace_filter
if backtrace_filter.respond_to?(:call)
filtered_backtrace = backtrace_filter.call(filtered_backtrace)
else
warn "IRB.conf[:BACKTRACE_FILTER] #{backtrace_filter} should respond to `call` method"
end
end
exc.set_backtrace(filtered_backtrace)
end
if RUBY_VERSION < '3.0.0'
if STDOUT.tty?
message = exc.full_message(order: :bottom)
order = :bottom
else
message = exc.full_message(order: :top)
order = :top
highlight = Color.colorable?
order =
if RUBY_VERSION < '3.0.0'
STDOUT.tty? ? :bottom : :top
else # '3.0.0' <= RUBY_VERSION
:top
end
else # '3.0.0' <= RUBY_VERSION
message = exc.full_message(order: :top)
order = :top
end
message = exc.full_message(order: order, highlight: highlight)
message = convert_invalid_byte_sequence(message, exc.message.encoding)
message = encode_with_invalid_byte_sequence(message, IRB.conf[:LC_MESSAGES].encoding) unless message.encoding.to_s.casecmp?(IRB.conf[:LC_MESSAGES].encoding.to_s)
message = message.gsub(/((?:^\t.+$\n)+)/) { |m|

Просмотреть файл

@ -196,7 +196,7 @@ module TestIRB
end
def write_ruby(program)
@ruby_file = Tempfile.create(%w{irb- .rb})
@ruby_file = Tempfile.create(%w{irbtest- .rb})
@tmpfiles << @ruby_file
@ruby_file.write(program)
@ruby_file.close

Просмотреть файл

@ -823,4 +823,95 @@ module TestIRB
IRB::Irb.new(workspace, TestInputMethod.new)
end
end
class BacktraceFilteringTest < TestIRB::IntegrationTestCase
def test_backtrace_filtering
write_ruby <<~'RUBY'
def foo
raise "error"
end
def bar
foo
end
binding.irb
RUBY
output = run_ruby_file do
type "bar"
type "exit"
end
assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output)
frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip)
expected_traces = if RUBY_VERSION >= "3.3.0"
[
/from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
/from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
/from <internal:kernel>:\d+:in (`|'Kernel#)loop'/,
/from <internal:prelude>:\d+:in (`|'Binding#)irb'/,
/from .*\/irbtest-.*.rb:9:in [`']<main>'/
]
else
[
/from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
/from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
/from <internal:prelude>:\d+:in (`|'Binding#)irb'/,
/from .*\/irbtest-.*.rb:9:in [`']<main>'/
]
end
expected_traces.reverse! if RUBY_VERSION < "3.0.0"
expected_traces.each_with_index do |expected_trace, index|
assert_match(expected_trace, frame_traces[index])
end
end
def test_backtrace_filtering_with_backtrace_filter
write_rc <<~'RUBY'
class TestBacktraceFilter
def self.call(backtrace)
backtrace.reject { |line| line.include?("internal") }
end
end
IRB.conf[:BACKTRACE_FILTER] = TestBacktraceFilter
RUBY
write_ruby <<~'RUBY'
def foo
raise "error"
end
def bar
foo
end
binding.irb
RUBY
output = run_ruby_file do
type "bar"
type "exit"
end
assert_match(/irbtest-.*\.rb:2:in (`|'Object#)foo': error \(RuntimeError\)/, output)
frame_traces = output.split("\n").select { |line| line.strip.match?(/from /) }.map(&:strip)
expected_traces = [
/from .*\/irbtest-.*.rb:6:in (`|'Object#)bar'/,
/from .*\/irbtest-.*.rb\(irb\):1:in [`']<main>'/,
/from .*\/irbtest-.*.rb:9:in [`']<main>'/
]
expected_traces.reverse! if RUBY_VERSION < "3.0.0"
expected_traces.each_with_index do |expected_trace, index|
assert_match(expected_trace, frame_traces[index])
end
end
end
end