Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent error formatting from causing recursive errors #1275

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
14 changes: 13 additions & 1 deletion lib/rspec/mocks/message_expectation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ def initialize(error_generator, expectation_ordering, expected_from, method_doub
@expectation_type = type
@ordered = false
@at_least = @at_most = @exactly = nil
@invoking = false

# Initialized to nil so that we don't allocate an array for every
# mock or stub. See also comment in `and_yield`.
Expand Down Expand Up @@ -424,7 +425,15 @@ def safe_invoke(parent_stub, *args, &block)
end

def invoke(parent_stub, *args, &block)
invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
if @invoking
safe_invoke_without_incrementing_received_count(parent_stub, *args, &block)
else
invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
end
end

def safe_invoke_without_incrementing_received_count(parent_stub, *args, &block)
invoke_incrementing_actual_calls_by(0, false, parent_stub, *args, &block)
end

def invoke_without_incrementing_received_count(parent_stub, *args, &block)
Expand Down Expand Up @@ -555,6 +564,8 @@ def exception_source_id
def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
args.unshift(orig_object) if yield_receiver_to_implementation_block?

@invoking = true

if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
# args are the args we actually received, @argument_list_matcher is the
# list of args we were expecting
Expand All @@ -574,6 +585,7 @@ def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub,
parent_stub.invoke(nil, *args, &block)
end
ensure
@invoking = false
@actual_received_count_write_mutex.synchronize do
@actual_received_count += increment
end
Expand Down
23 changes: 23 additions & 0 deletions spec/rspec/mocks/partial_double_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,29 @@ def self.inspect
}.to fail_with(/MyClass/)
end

it "formats an error message when inspect fails" do
klass = Struct.new(:name) do
def inspect
"<Inspector(#{name})>"
end
end

object = klass.new('foo')
expect(object).to receive(:name).once.and_return('bar')
object.name

expect {
object.name
}.to fail_with(
%|(<Inspector(bar)>).name(no args)\n expected: 1 time with any arguments\n received: 2 times|
)
expect {
verify object
}.to fail_with(
%|(<Inspector(bar)>).name(*(any args))\n expected: 1 time with any arguments\n received: 2 times with any arguments|
)
end

it "shares message expectations with clone" do
expect(object).to receive(:foobar)
twin = object.clone
Expand Down