зеркало из https://github.com/github/ruby.git
[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:
Родитель
2a978ee047
Коммит
1000c27db8
40
lib/irb.rb
40
lib/irb.rb
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче