-
-
Notifications
You must be signed in to change notification settings - Fork 103
Fix diff output when a fuzzy finder anything is inside an expected hash #599
Changes from 6 commits
b993756
4c22e70
048ac0a
c3e9ea9
416cd49
0dea769
31f086e
dd7a4c7
938096c
7046659
6b7ff69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -18,6 +18,8 @@ def diff(actual, expected) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if any_multiline_strings?(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
diff = diff_as_string(coerce_to_string(actual), coerce_to_string(expected)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
elsif all_hashes?(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
diff = diff_hashes_as_object(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
elsif no_procs?(actual, expected) && no_numbers?(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
diff = diff_as_object(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -56,6 +58,26 @@ def diff_as_string(actual, expected) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# rubocop:enable Metrics/MethodLength | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def diff_hashes_as_object(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if defined?(RSpec::Mocks::ArgumentMatchers::AnyArgMatcher) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anything_hash = expected.select { |_, v| RSpec::Mocks::ArgumentMatchers::AnyArgMatcher === v } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anything_hash.each_key do |k| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expected[k] = actual[k] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
diff_string = diff_as_object(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anything_hash.each do |k, v| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expected[k] = v | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
diff_string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
diff_as_object(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typically we define such methods conditionally rather than doing checks in the method e.g.
Suggested change
I'd also like to see a hash built from expected rather than mutating the original hash e.g.
This has the benefit of less enumerations as well as a safety aspect |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def diff_as_object(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
actual_as_string = object_to_string(actual) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expected_as_string = object_to_string(expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -77,6 +99,10 @@ def no_procs?(*args) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
safely_flatten(args).none? { |a| Proc === a } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I really liked about your previous changes was the potential to get rid of the recursive check and the safely_flatten method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand, it would be nice to get rid of that recursivity. You can count on me to brainstorm on another PR or submit a proposal to fix that issue. I am enjoying reading and getting my hands on this project, and once this PR is finished (by finished I mean either merged or closed (if you and the other maintainers believe the changes I'm proposing here are too risky, or not worth doing them)) I'd like to propose some changes to make the About your concern with Just some brainstorming and ramble here: Is it worth to keep that recursivity? when an On the project I am currently working on, when I'm testing the actual and expected values of a hash with nested hashes inside, I've found is useful to strip my actual hash of its nested objects in my test set-up, and inside an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can’t tell of the top of my head with certainty, but I can’t recall if nested hashes are (usefully) diffed. I’d rather simplify the code if no spec fails. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def all_hashes?(actual, expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defined?(RSpec::Mocks::ArgumentMatchers::AnyArgMatcher) && (Hash === actual) && (Hash === expected) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't check if we have an any arg here, the other method already does this.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def all_strings?(*args) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
safely_flatten(args).all? { |a| String === a } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -555,6 +555,30 @@ def inspect; "<BrokenObject>"; end | |
expect(differ.diff(false, true)).to_not be_empty | ||
end | ||
end | ||
|
||
describe "fuzzy matcher anything" do | ||
it "outputs only key value pair that triggered diff, anything_key should absorb actual value" do | ||
actual = { :fixed => "fixed", :trigger => "trigger", :anything_key => "bcdd0399-1cfe-4de1-a481-ca6b17d41ed8" } | ||
expected = { :fixed => "fixed", :trigger => "wrong", :anything_key => anything } | ||
diff = differ.diff(actual, expected) | ||
expected_diff = dedent(<<-'EOD') | ||
| | ||
|@@ -1,4 +1,4 @@ | ||
| :anything_key => "bcdd0399-1cfe-4de1-a481-ca6b17d41ed8", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now when I look at this, this is indeed and improvement to Most certainly if we’ve @JonRowe does this make sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you concerned by the case a 5kb json-like thing would be copied into the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a shallow copy will not impact memory ram consumption. But what about CPU time? does Another question: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apparently, a 5kb json-like thing will impact CPU time ONLY if the matcher fails. def diff_as_object(actual, expected)
actual_as_string = object_to_string(actual) # STEP 1
expected_as_string = object_to_string(expected) # STEP 2
diff_as_string(actual_as_string, expected_as_string) # STEP 3
end STEP 1 and STEP 2 will get the string representation of 5kb json-like thing in two different strings. STEP 3 will perform the comparison of both strings. This will be an expensive operation because both strings will be huge. In contrast, without the changes on this PR, comparing the string representation of this 5kb json-like thing with AFAIK, My assessment is this will impact performance only when the test fails and the developer is using a matcher that produces an output-diff. But this is my assessment about performance, I still don't know the answer to: should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think @pirj's concern was about larger diffs, but as things should be identical the differ will often not print anything at all There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Identical - yes. But if it’s neighbouring with the difference, like the ‘anything_key’ here on line 567, and it is big, i would prefer to see the literal “anything” to be printed rather than 5kb of json. |
||
| :fixed => "fixed", | ||
|-:trigger => "wrong", | ||
|+:trigger => "trigger", | ||
| | ||
EOD | ||
expect(diff).to be_diffed_as(expected_diff) | ||
end | ||
it "checks the 'expected' var continues having the 'anything' fuzzy matcher, it has not mutated" do | ||
JonRowe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
actual = { :fixed => "fixed", :trigger => "trigger", :anything_key => "bcdd0399-1cfe-4de1-a481-ca6b17d41ed8" } | ||
expected = { :fixed => "fixed", :trigger => "wrong", :anything_key => anything } | ||
differ.diff(actual, expected) | ||
expect(expected).to eq({ :fixed => "fixed", :trigger => "wrong", :anything_key => anything }) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only call this from all_hashes?, and it already has this check
Potentially, this method can be mistakenly called from somewhere else causing errors for those not using rspec-mocks, but i’m less worried about this at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is currently called only under
if all_hashes?(actual, expected)
condition, andall_hashes?
is also checking if the user is opting outrspec-mocks
. This check in line 62 is redundant if we assume this method will only be called insideall_hashes?
condition . But it is there in case someone mistakenly call the method from somewhere else, so the program won't break ifrspec-mocks
is opted-out.If someone opts-out
rspec-mock
,diff_hashes_as_object
should have the exact same behavior asdiff_as_object
. That's the purpose of this redundant check.Personally, I like redundancy. But I understand sometimes it may be overkill or unnecessary... maybe defining
diff_hashes_as_object
as private is good enough? I don't have a strong opinion here.