Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

display nested exception causes #2075

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions lib/rspec/core/formatters/exception_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
end

def formatted_backtrace
backtrace_formatter.format_backtrace(exception_backtrace, example.metadata)
backtrace_formatter.format_backtrace(backtrace_presenter.backtrace, example.metadata)
end

def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
formatted_backtrace.map do |backtrace_info|
colorizer.wrap "# #{backtrace_info}", RSpec.configuration.detail_color
colorizer.wrap "# #{backtrace_info}".strip, RSpec.configuration.detail_color
end
end

Expand All @@ -54,6 +54,10 @@ def failure_slash_error_line
@failure_slash_error_line ||= "Failure/Error: #{read_failed_line.strip}"
end

def backtrace_presenter
@backtrace_presenter ||= BacktracePresenter.new(exception)
end

private

def description_and_detail(colorizer, indentation)
Expand Down Expand Up @@ -152,6 +156,59 @@ def exception_backtrace
exception.backtrace || []
end

# @private
# Format exception backtrace, including nested "cause" exception(s)
class BacktracePresenter
attr_reader :exception, :nested

def initialize(exception, nested=false)
@exception = exception
@nested = nested
end

def backtrace
trace = separator
trace.concat exception_backtrace

if exception.respond_to?(:cause)
cause = exception.cause
if cause
trace.concat(BacktracePresenter.new(cause, true).backtrace)
end
end

trace
end

private

def separator
if nested
['', '--- Caused by: ---', exception_class_name, exception_message].compact
else
[]
end
end

def exception_class_name
class_name = exception.class.name.to_s
if class_name =~ /RSpec/
class_name = nil
else
class_name << ":"
end
class_name
end

def exception_message
" #{exception.message}"
end

def exception_backtrace
exception.backtrace || []
end
end

# @private
# Configuring the `ExceptionPresenter` with the right set of options to handle
# pending vs failed vs skipped and aggregated (or not) failures is not simple.
Expand Down
20 changes: 20 additions & 0 deletions spec/rspec/core/formatters/exception_presenter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,26 @@ module RSpec::Core
| extra detail for failure: 2
EOS
end

context 'when cause is set to nested exception' do
let(:nested) { instance_double(Exception, :message => "The real problem", :backtrace => ["#{__FILE__}:#{line_num}"], :class => RuntimeError) }
let(:exception) { instance_double(Exception, :message => "The top level", :backtrace => ["#{__FILE__}:#{line_num}"], :cause => nested) }

it 'displays nested exception' do
expect(presenter.fully_formatted(2)).to eq(<<-EOS.gsub(/^ +\|/, ''))
|
| 2) Example
| Failure/Error: # The failure happened here!#{ encoding_check }
| The top level
| # ./spec/rspec/core/formatters/exception_presenter_spec.rb:#{line_num}
| #
| # --- Caused by: ---
| # RuntimeError:
| # The real problem
| # ./spec/rspec/core/formatters/exception_presenter_spec.rb:#{line_num}
EOS
end
end
end

describe "#read_failed_line" do
Expand Down