Skip to content

Commit

Permalink
Prevent invalid cause from breaking exception presenter
Browse files Browse the repository at this point in the history
  • Loading branch information
JonRowe committed Mar 10, 2020
1 parent 5de10ff commit fa95aeb
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 11 deletions.
5 changes: 3 additions & 2 deletions lib/rspec/core/formatters/exception_presenter.rb
Expand Up @@ -43,7 +43,7 @@ def formatted_backtrace(exception=@exception)

if RSpec::Support::RubyFeatures.supports_exception_cause?
def formatted_cause(exception)
last_cause = final_exception(exception)
last_cause = final_exception(exception, [exception])
cause = []

if exception.cause
Expand Down Expand Up @@ -96,7 +96,8 @@ def fully_formatted_lines(failure_number, colorizer)

def final_exception(exception, previous=[])
cause = exception.cause
if cause && !previous.include?(cause)

if cause && Exception === cause && !previous.include?(cause)
previous << cause
final_exception(cause, previous)
else
Expand Down
57 changes: 48 additions & 9 deletions spec/rspec/core/formatters/exception_presenter_spec.rb
Expand Up @@ -11,7 +11,22 @@ module RSpec::Core
before do
allow(example.execution_result).to receive(:exception) { exception }
example.metadata[:absolute_file_path] = __FILE__
allow(exception).to receive(:cause) if RSpec::Support::RubyFeatures.supports_exception_cause?
end

# This is a slightly more realistic exception than our instance_double
# created, as this will behave correctly with `Exception#===`, note we
# monkey patch the backtrace / cause in because these are not public
# api but we need specific values for our fakes.
class FakeException < Exception
def initialize(message, backtrace = [], cause = nil)
super(message)
@backtrace = backtrace
@cause = cause
end
attr_reader :backtrace
if RSpec::Support::RubyFeatures.supports_exception_cause?
attr_reader :cause
end
end

describe "#fully_formatted" do
Expand All @@ -25,7 +40,7 @@ module RSpec::Core
line_num = __LINE__ + 1
# The failure happened here! Handles encoding too! ЙЦ
end
let(:exception) { instance_double(Exception, :message => "Boom\nBam", :backtrace => [ "#{__FILE__}:#{line_num}"]) }
let(:exception) { FakeException.new("Boom\nBam", [ "#{__FILE__}:#{line_num}"]) }

it "formats the exception with all the normal details" do
expect(presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
Expand Down Expand Up @@ -159,16 +174,14 @@ module RSpec::Core
EOS
end

let(:the_exception) { instance_double(Exception, :cause => second_exception, :message => "Boom\nBam", :backtrace => [ "#{__FILE__}:#{line_num}"]) }
let(:the_exception) { FakeException.new("Boom\nBam", [ "#{__FILE__}:#{line_num}"], second_exception) }

let(:second_exception) do
instance_double(Exception, :cause => first_exception, :message => "Second\nexception", :backtrace => ["#{__FILE__}:#{__LINE__}"])
FakeException.new("Second\nexception", ["#{__FILE__}:#{__LINE__}"], first_exception)
end

caused_by_line_num = __LINE__ + 2
let(:first_exception) do
instance_double(Exception, :cause => nil, :message => "Real\nculprit", :backtrace => ["#{__FILE__}:#{__LINE__}"])
end
caused_by_line_num = __LINE__ + 1
let(:first_exception) { FakeException.new("Real\nculprit", ["#{__FILE__}:#{__LINE__}"]) }

it 'includes the first exception that caused the failure', :if => RSpec::Support::RubyFeatures.supports_exception_cause? do
the_presenter = Formatters::ExceptionPresenter.new(the_exception, example)
Expand Down Expand Up @@ -211,8 +224,9 @@ module RSpec::Core

it 'wont produce a stack error when the cause is an older exception', :if => RSpec::Support::RubyFeatures.supports_exception_cause? do
allow(the_exception).to receive(:cause) do
instance_double(Exception, :cause => the_exception, :message => "A loop", :backtrace => the_exception.backtrace)
FakeException.new("A loop", the_exception.backtrace, the_exception)
end

the_presenter = Formatters::ExceptionPresenter.new(the_exception, example)

expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
Expand All @@ -230,6 +244,31 @@ module RSpec::Core
EOS
end

it 'will work when cause is incorrectly overridden', :if => RSpec::Support::RubyFeatures.supports_exception_cause? do
exception_klass = Class.new(Exception) do
def cause
"An incorrect implementation"
end
end
incorrect_cause_exception = exception_klass.new

the_presenter = Formatters::ExceptionPresenter.new(incorrect_cause_exception, example)

expect(the_presenter.fully_formatted(1)).to eq(<<-EOS.gsub(/^ +\|/, ''))
|
| 1) Example
| Failure/Error: Unable to find matching line from backtrace
|
| (anonymous error class):
| #{exception_klass.inspect}
| # ------------------
| # --- Caused by: ---
| # (anonymous error class):
| # #{exception_klass.inspect}
| #
EOS
end

it "adds extra failure lines from the example metadata" do
extra_example = example.clone
failure_line = 'http://www.example.com/job_details/123'
Expand Down

0 comments on commit fa95aeb

Please sign in to comment.