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 Aliased Matchers from Overriding Expected Data #1116

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

lnestor
Copy link

@lnestor lnestor commented May 24, 2019

Previous Behavior

The aliased matcher implementation of a description currently replaces all instance of the old matcher's name in the new matcher's name. If it happens that the expected values include the old matcher's name, these also get overwritten. In the examples below, the string "include" is replaced in both the outputs by the new matcher's name.

it 'overwrites data for a_string_including' do
  my_string = "a string with 'include'"
  expect("some random string").to match(a_string_including(my_string))
end

# => expected "some random string" to match 
#       (a string including "a string with 'a string including'")
it 'overwrites data for a_hash_including' do
  my_hash = { my_string: "a string with 'include'" }
  expect({}).to match(a_hash_including(my_hash))
end

# => expected {} to match 
#       (a hash including {:my_string => "a string with 'a hash including'"})

New Behavior

This change makes it so it only replaces the first instance of the original name so none of the expected data is also overwritten. The two above error outputs would be the following:

expected {} to match (a hash including {:my_string => "a string with 'include'"})
expected "some random string" to match (a string including "a string with 'include'")

I saw this in rspec/rspec-rails#1835.

The aliased matcher implementation of a description currently
replaces all instance of the old matcher's name with the new
matcher's name. However, in cases where the expected value
has the same string as the old matcher's name, it would also
get replaced. This change makes it so only the first instance
of the old matcher's name is replaced in the description.
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.

Thanks for tackling this, a bit of feedback above!

@@ -29,7 +29,7 @@ module DSL
# @see RSpec::Matchers
def alias_matcher(new_name, old_name, options={}, &description_override)
description_override ||= lambda do |old_desc|
old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
old_desc.sub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
Copy link
Member

Choose a reason for hiding this comment

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

I think this needs to only match the start of the string, this might still accidentally override terms if the original description is not generated.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the feedback! Can you clarify this a bit? #sub matches the first occurrence, so are you saying something like matching the first n characters of the string?

Also, what do you mean by if the original description is not generated? Since BaseMatcher implements description, I would think that they all would have a description. Unless a class implements its own description which does not contain the name of the matcher.

Copy link
Member

Choose a reason for hiding this comment

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

Can you clarify this a bit? #sub matches the first occurrence, so are you saying something like matching the first n characters of the string?

"hello_old_name".sub("old_name","new_name") would produce "hello_new_name" but we don't want that. We only want to replace old_name as it was generated initially.

Also, what do you mean by if the original description is not generated? Since BaseMatcher implements description, I would think that they all would have a description.

Similarly it is possible for matchers to implement description themselves and it might mean they don't contain the old_name at all, in this case we shouldn't be replacing the first instance blindly as it might not be in the description but in the expected.


it 'does not override data in the description with the same name as the matcher' do
matcher = my_repeating_override
expect(matcher.description).to eq("my repeating override my repeating base matcher")
Copy link
Member

Choose a reason for hiding this comment

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

It'd prefer a practical example showing the problem using fail_with, even the one from rspec/rspec-rails#1835 would suffice.

@lnestor lnestor force-pushed the repeating-aliased-matcher-descriptions branch from 8279d26 to 7e54f1f Compare May 31, 2019 19:29
Lucas Nestor added 2 commits May 31, 2019 16:32
A flag was added when generating an aliased matcher's
description to better control how the old matcher's name
is replaced. However, this broke the existing API for
overriding a matcher's new description. This class allows
to maintain backwards compatability gracefully.
old_desc
end
end
end
Copy link
Member

Choose a reason for hiding this comment

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

This is a lot of added complication, and still doesn't remove the global sub in favour of a "start of string only" replace.

@pirj
Copy link
Member

pirj commented Jan 11, 2020

Do you have any questions on how to proceed with this @lnestor ?

@pirj
Copy link
Member

pirj commented Jun 11, 2020

@lnestor Are you interested in wrapping this up?

@JonRowe JonRowe changed the base branch from master to main August 2, 2020 02:06
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.

3 participants