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

Allow matchers to be reused #1326

Merged
merged 1 commit into from
Apr 26, 2022

Conversation

bclayman-sq
Copy link
Contributor

This addresses issue #1287.

Previously, matchers retained state after being used as an argument to RSpec::Expectations::ExpectationTarget#to. In particular, it leaked @extra_items, @missing_items, @best_solution, and @pairings_maximizer. This caused incorrect results when reusing a matcher in subsequent expectations.

This PR addresses the issue by reinitializing the matcher's state when the matcher is reused.

Co-authored by: Rai Feren rferen@squareup.com

@bclayman-sq bclayman-sq force-pushed the bclayman/allow-matcher-reuse branch 2 times, most recently from e9dcb9b to b2d8d78 Compare October 13, 2021 13:21
@bclayman-sq
Copy link
Contributor Author

@pirj Thanks for kicking off that build! It looks like this PR passes for all CI environments except Ruby 2.2 on Windows. I looked at the logs but didn't see much info that would be helpful in debugging (I see an error, but no failing tests). Could you help me understand what might have happened here?

Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like that the original problem is caused by rose memoization @... ||=.
Even though other built-in matchers use it, too, I couldn't make them cache anything between usages.

I could suggest localizing the change to contain_exactly, but that would require using an additional instance variable (@match_called_previously?) in match, as @actual would be always defined there, or overriding matches?. Unless you have a more elegant solution, let's keep the call to reinitialize in BaseMatcher.

And thanks for yet another contribution!

lib/rspec/matchers/built_in/base_matcher.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/base_matcher.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/base_matcher.rb Outdated Show resolved Hide resolved
spec/rspec/matchers/composable_spec.rb Outdated Show resolved Hide resolved
@bclayman-sq
Copy link
Contributor Author

bclayman-sq commented Oct 15, 2021

It seems like that the original problem is caused by rose memoization @... ||=. Even though other built-in matchers use it, too, I couldn't make them cache anything between usages.

I could suggest localizing the change to contain_exactly, but that would require using an additional instance variable (@match_called_previously?) in match, as @actual would be always defined there, or overriding matches?. Unless you have a more elegant solution, let's keep the call to reinitialize in BaseMatcher.

And thanks for yet another contribution!

👋 @pirj Happy to! I just incorporated your feedback, thanks for the review! Do you have any insight into why ruby 2.2 on windows fails in CI? I don't see any failing tests, just an error code in the logs...

Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thank you!

@pirj
Copy link
Member

pirj commented Oct 15, 2021

why ruby 2.2 on windows fails in CI

It segfaulted last time, passed this time, and crashed with no message the first time. I don't think it is caused by your changes or can be fixed. It is not a blocker to merge this PR.

@bclayman-sq
Copy link
Contributor Author

@pirj @JonRowe 👋 Anything I can do to get this PR merged?

Copy link
Member

@JonRowe JonRowe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a bug with only the RSpec::Matchers::BuiltIn::ContainExactly matcher, how about just overriding matches?(actual) rather than adding a method to every matcher?

Comment on lines 37 to 39
# if @actual is already set, we know this matcher was used previously and want
# to reset memoized fields
reset if defined?(@actual)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# if @actual is already set, we know this matcher was used previously and want
# to reset memoized fields
reset if defined?(@actual)

Comment on lines 132 to 133
def reset; end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def reset; end

Comment on lines 34 to 40
private

def reset
@pairings_maximizer = nil
@best_solution = nil
@extra_items = nil
@missing_items = nil
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private
def reset
@pairings_maximizer = nil
@best_solution = nil
@extra_items = nil
@missing_items = nil
end
def matches?(actual)
@pairings_maximizer = nil
@best_solution = nil
@extra_items = nil
@missing_items = nil
super(actual)
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JonRowe Yep, this is even nicer than my approach: it keeps the change local to ContainExactly. Just incorporated. Mind kicking off a build?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more build and should be good to go!

This addresses issue rspec#1287.

Previously, matchers retained state after being used as an argument
to RSpec::Expectations::ExpectationTarget#to.  This caused
incorrect results when reusing a matcher in subsequent
expectations.

This PR addresses the issue by overriding ContainExactly#matches?,
reinitializing its internal state when the matcher is reused.

Co-authored by: Rai Feren <rferen@squareup.com>
@JonRowe JonRowe merged commit d62d266 into rspec:main Apr 26, 2022
JonRowe added a commit that referenced this pull request Apr 26, 2022
JonRowe added a commit that referenced this pull request Apr 26, 2022
JonRowe added a commit that referenced this pull request Apr 26, 2022
@pirj
Copy link
Member

pirj commented Apr 26, 2022

Thank you!

pirj pushed a commit that referenced this pull request Nov 1, 2022
pirj pushed a commit that referenced this pull request Nov 1, 2022
JonRowe added a commit that referenced this pull request Nov 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants