Closed sergio-bobillier closed 2 months ago
A wild guess. What if you remove this line? expect(Mutex).to receive(:new)
The result is the same with or without the expectation. I believe this condition arises from the setup in the before
block.
This is different to the original bug and weirder. That was about the ability to stub mutex at all. The call that causes the problem here is the stubbing of the synchronize method, if you remove:
allow(mocked_mutex).to receive(:synchronize).and_yield
The spec runs [or at least the version I set up in rspec-support
does]
@JonRowe Thanks, yes, that actually removes the issue, but the thing is that would also keep me from testing what I want.
The code I added was the minimum required to trigger the issue, however I'm interested in also testing what happens inside the synchronize
block. In my tests I'm checking that some actions are performed under the "protection" of the Mutex. If I remove the allow
then that code will never be executed.
It is not even possible to test that synchronize
is being called over the Mutex
instance, even without the #and_yield
call the "Stack level too deep" error occurs.
Even if I remove the allow
completely, any test checking the call to synchronize
will trigger the same error, for example:
spec/writer_spec.rb
:
# frozen_string_literal: true
require_relative '../writer'
RSpec.describe Writer do
subject(:writer) { described_class.new }
let(:mocked_mutex) do
instance_double(
Mutex
)
end
before do
allow(Mutex).to receive(:new).and_return(mocked_mutex)
end
describe '#initialize' do
it 'creates a new Mutex' do
expect(Mutex).to receive(:new)
writer
end
end
describe '#write' do
it 'uses the Mutex to synchronize the writing' do
expect(mocked_mutex).to receive(:synchronize)
writer.write("hello world!")
end
end
end
Now the test for #initialize
passes, but the one for #write
triggers the exact same issue.
And note that, just setting the expectation triggers the error, even if I remove the call to the actual method: writer.write("hello world!")
the error will still be raised.
Apologies, I did not mean to imply you should remove the line, my intent was to highlight that it was the synchronize
stub which was the root of the issue (to differnitate it from previous issues), as an aside your updated spec is essentially the same, its (likely) not a combination of allow and expect thats causing it.
We seem to be using synchronize
in our code, and if it’s mocked to return a canned response - the behaviour is unpredictable.
Should we stash it for ourselves, too?
I don't think thats the issue, we stash new
so that our instances of mutex shouldn't have that stub, and indeed I tried a version of where I did the same treatment to all methods with no changes. So either something is broken with how we stub / capture mutex on this or its a more complicated interaction, my quick investigation looked like it might be method visibility related
I'm not sure I correctly understand how the 'stashing' is intended to operate - is this the code that should be doing that? If so, at this point, Mutex
is already defined, so the unless
skips that block - Mutex
is always defined now. I suspect that it was intended to check if RSpec::Mocks::Mutex
was yet defined, since that's the constant that it then defines (indeed, if there weren't a top-level Mutex constant, this would be checking that.)
Updating this line to
unless defined?(Proxy::Mutex)
Passes the spec, and appears to perhaps reflect the intent - the lines were introduced here, and it doesn't look like it intended anything tricky.
Here's my stab, though I think that the spec might not be in the right place: https://github.com/rspec/rspec-mocks/pull/1575
I'm not sure I correctly understand how the 'stashing' is intended to operate I mentioned this on the PR too but the code that I refer to as "stashing" is this https://github.com/rspec/rspec-support/blob/main/lib/rspec/support/reentrant_mutex.rb#L68
Fix released in 3.13.1
Mutex cannot be mocked since
Since
rspec 3.9.0
mocks of theMutex
class result in aSystemStackError
: stack level too deep. Error. I unearthed these two issues:And I thought this had already been fixed and it was something related to my setup or the combination of gems in my project but actually the issue is still there. Reproducible with Ruby 2.x and 3.x
Your environment
3.2.2
and2.7.7
3.9.0
all the way up to3.13.0
the issue is visible in all of them.Steps to reproduce
I created this simple project to show the issue:
writer.rb
:spec/writer_spec.rb
:Expected behavior
I would expect my test to pass (like it does with RSpec
3.8.0
)Actual behavior
I'm getting the mentioned
SystemStackError: stack level too deep
with such a backtrace: