Skip to content

Commit 361f5d1

Browse files
committed
Merge pull request rspec#2074 from mrageh/ma-include-exception-that-caused-stacktrace-in-exception-backtrace
Include exception that raised failure in backtrace
2 parents c7e95f9 + b5a1e42 commit 361f5d1

File tree

3 files changed

+79
-14
lines changed

3 files changed

+79
-14
lines changed

lib/rspec/core/formatters/exception_presenter.rb

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,36 @@ def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
3131
end
3232
end
3333

34-
def formatted_backtrace
35-
backtrace_formatter.format_backtrace(exception_backtrace, example.metadata)
34+
def formatted_backtrace(exception=@exception)
35+
backtrace_formatter.format_backtrace((exception.backtrace || []), example.metadata) +
36+
formatted_cause(exception)
37+
end
38+
39+
if RSpec::Support::RubyFeatures.supports_exception_cause?
40+
def formatted_cause(exception)
41+
last_cause = final_exception(exception)
42+
cause = []
43+
44+
if exception.cause
45+
cause << '------------------'
46+
cause << '--- Caused by: ---'
47+
cause << "#{exception_class_name(last_cause)}:" unless exception_class_name(last_cause) =~ /RSpec/
48+
49+
encoded_string(last_cause.message.to_s).split("\n").each do |line|
50+
cause << " #{line}"
51+
end
52+
53+
cause << (" #{backtrace_formatter.format_backtrace(last_cause.backtrace, example.metadata).first}")
54+
end
55+
56+
cause
57+
end
58+
else
59+
# :nocov:
60+
def formatted_cause(_)
61+
[]
62+
end
63+
# :nocov:
3664
end
3765

3866
def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
@@ -56,6 +84,14 @@ def failure_slash_error_line
5684

5785
private
5886

87+
def final_exception(exception)
88+
if exception.cause
89+
final_exception(exception.cause)
90+
else
91+
exception
92+
end
93+
end
94+
5995
def description_and_detail(colorizer, indentation)
6096
detail = detail_formatter.call(example, colorizer, indentation)
6197
return (description || detail) unless description && detail
@@ -81,7 +117,7 @@ def encoded_string(string)
81117
# :nocov:
82118
end
83119

84-
def exception_class_name
120+
def exception_class_name(exception=@exception)
85121
name = exception.class.name.to_s
86122
name = "(anonymous error class)" if name == ''
87123
name

spec/rspec/core/formatters/exception_presenter_spec.rb

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module RSpec::Core
1111
before do
1212
allow(example.execution_result).to receive(:exception) { exception }
1313
example.metadata[:absolute_file_path] = __FILE__
14+
allow(exception).to receive(:cause) if RSpec::Support::RubyFeatures.supports_exception_cause?
1415
end
1516

1617
describe "#fully_formatted" do
@@ -49,9 +50,9 @@ module RSpec::Core
4950
end
5051

5152
it "allows the caller to specify additional indentation" do
52-
presenter = Formatters::ExceptionPresenter.new(exception, example, :indentation => 4)
53+
the_presenter = Formatters::ExceptionPresenter.new(exception, example, :indentation => 4)
5354

54-
expect(presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
55+
expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
5556
|
5657
| 1) Example
5758
| Failure/Error: # The failure happened here!#{ encoding_check }
@@ -64,9 +65,9 @@ module RSpec::Core
6465
it 'passes the indentation on to the `:detail_formatter` lambda so it can align things' do
6566
detail_formatter = Proc.new { "Some Detail" }
6667

67-
presenter = Formatters::ExceptionPresenter.new(exception, example, :indentation => 4,
68+
the_presenter = Formatters::ExceptionPresenter.new(exception, example, :indentation => 4,
6869
:detail_formatter => detail_formatter)
69-
expect(presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
70+
expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
7071
|
7172
| 1) Example
7273
| Some Detail
@@ -78,11 +79,11 @@ module RSpec::Core
7879
end
7980

8081
it 'allows the caller to omit the description' do
81-
presenter = Formatters::ExceptionPresenter.new(exception, example,
82+
the_presenter = Formatters::ExceptionPresenter.new(exception, example,
8283
:detail_formatter => Proc.new { "Detail!" },
8384
:description_formatter => Proc.new { })
8485

85-
expect(presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
86+
expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
8687
|
8788
| 1) Detail!
8889
| Failure/Error: # The failure happened here!#{ encoding_check }
@@ -93,9 +94,9 @@ module RSpec::Core
9394
end
9495

9596
it 'allows the failure/error line to be used as the description' do
96-
presenter = Formatters::ExceptionPresenter.new(exception, example, :description_formatter => lambda { |p| p.failure_slash_error_line })
97+
the_presenter = Formatters::ExceptionPresenter.new(exception, example, :description_formatter => lambda { |p| p.failure_slash_error_line })
9798

98-
expect(presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
99+
expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
99100
|
100101
| 1) Failure/Error: # The failure happened here!#{ encoding_check }
101102
| Boom
@@ -105,13 +106,13 @@ module RSpec::Core
105106
end
106107

107108
it 'allows a caller to specify extra details that are added to the bottom' do
108-
presenter = Formatters::ExceptionPresenter.new(
109+
the_presenter = Formatters::ExceptionPresenter.new(
109110
exception, example, :extra_detail_formatter => lambda do |failure_number, colorizer, indentation|
110111
"#{indentation}extra detail for failure: #{failure_number}\n"
111112
end
112113
)
113114

114-
expect(presenter.fully_formatted(2)).to eq(<<-EOS.gsub(/^ +\|/, ''))
115+
expect(the_presenter.fully_formatted(2)).to eq(<<-EOS.gsub(/^ +\|/, ''))
115116
|
116117
| 2) Example
117118
| Failure/Error: # The failure happened here!#{ encoding_check }
@@ -121,8 +122,35 @@ module RSpec::Core
121122
| extra detail for failure: 2
122123
EOS
123124
end
124-
end
125125

126+
let(:the_exception) { instance_double(Exception, :cause => second_exception, :message => "Boom\nBam", :backtrace => [ "#{__FILE__}:#{line_num}"]) }
127+
128+
let(:second_exception) do
129+
instance_double(Exception, :cause => first_exception, :message => "Second\nexception", :backtrace => ["#{__FILE__}:#{__LINE__}"])
130+
end
131+
132+
let(:first_exception) do
133+
instance_double(Exception, :cause => nil, :message => "Real\nculprit", :backtrace => ["#{__FILE__}:#{__LINE__}"])
134+
end
135+
136+
it 'includes the first exception that caused the failure', :if => RSpec::Support::RubyFeatures.supports_exception_cause? do
137+
the_presenter = Formatters::ExceptionPresenter.new(the_exception, example)
138+
139+
expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
140+
|
141+
| 1) Example
142+
| Failure/Error: # The failure happened here!#{ encoding_check }
143+
| Boom
144+
| Bam
145+
| # ./spec/rspec/core/formatters/exception_presenter_spec.rb:#{line_num}
146+
| # ------------------
147+
| # --- Caused by: ---
148+
| # Real
149+
| # culprit
150+
| # ./spec/rspec/core/formatters/exception_presenter_spec.rb:133
151+
EOS
152+
end
153+
end
126154
describe "#read_failed_line" do
127155
def read_failed_line
128156
presenter.send(:read_failed_line)

spec/rspec/core/notifications_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
end
1919

2020
it 'provides `colorized_formatted_backtrace`, which formats the backtrace and colorizes it' do
21+
allow(exception).to receive(:cause) if RSpec::Support::RubyFeatures.supports_exception_cause?
2122
allow(RSpec.configuration).to receive(:color_enabled?).and_return(true)
2223
expect(notification.colorized_formatted_backtrace).to eq(["\e[36m# #{RSpec::Core::Metadata.relative_path(__FILE__)}:#{exception_line}\e[0m"])
2324
end

0 commit comments

Comments
 (0)