-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
fixed asymmetrical equality of cyclic objects #7730
fixed asymmetrical equality of cyclic objects #7730
Conversation
Thanks! Mind updating the changelog as well? |
I'm not convinced that |
Updated the changelog. This is the only one The implementation is quite easy too. Could be achieved by changing one line in Jest's current equality function. I checked and all tests passed. I could not find any threads discussing this case. Personally, I think they equal only if the cyclic reference is at the same depth in both objects. I also get that |
Codecov Report
@@ Coverage Diff @@
## master #7730 +/- ##
=========================================
+ Coverage 68.38% 68.4% +0.02%
=========================================
Files 253 253
Lines 9723 9726 +3
Branches 5 5
=========================================
+ Hits 6649 6653 +4
+ Misses 3072 3071 -1
Partials 2 2
Continue to review full report at Codecov.
|
Should get someone with high reach to Twitter poll this for a highly scientific evaluation of the two options ^^ |
@pedrottimark @thymikee @rickhanlonii anyone with a strong opinion about this? 😄 |
Please forgive the delay to review this important pull request. I verified that code is adapted from Underscore and in version 1.9.1 To make sure I review according to correct assumptions, corresponding properties:
It looks like recent versions of node provide a test that behaves correctly:
Is that from where you adapted the code? While I dig into the code, let’s add a few more tests:
I expected node to consider these equal: const a = {};
a.x = { y: a };
const b = {};
const bx = {};
b.x = bx;
bx.y = bx; because
but it considers them unequal, so let’s see what the updated code for Jest does in that case. |
Thank you for your review. Yes, I have adapted the code from the latest version of node. There is one more assumption:
I will demonstrate with your example. const a = {};
a.x = { y: a };
const b = {};
const bx = {};
b.x = bx;
bx.y = bx; In { x: { y: [CircularTo0] } }
{ x: { y: [CircularTo1] } } The position is important because if we change { x: { y: { x: { y: [Circular] } } } }
{ x: { y: { y: [Circular] } } } This is my mental model. I expect that if These are just my assumptions and I might be missing something. I looked into node's test cases and discovered that they do not have the same assumptions. They consider this case to be equal. const a = {};
const b = {};
a.a = a;
b.a = {};
b.a.a = a;
{ a: [Circular] }
{ a: { a: { a: [Circular] } } } Currently, algorithm in this PR behaves exactly the same way as node's one. I will make adjustments and add extra tests later today or tomorrow. |
BTW this is the part where I disagree about the expected behavior. I expect (This is such a perfect topic for bikeshedding 👌😅) |
Updated tests and algorithm. |
https://twitter.com/_jeysal_/status/1092792746488799233 can get us an overview of what's intuitive for most people, RTed by @cpojer and @rickhanlonii for reach |
@jeysal tbh the example code in the tweet will affect the perception without more context. most of the time the object will come from some other function, and how the object was created won't matter. i think the behaviour described in jest's documentation |
@satya164 Twitter polls will always have some flaws, but I think it's still a good way to find out about intuition. If we discuss in more detail here and get to agree on some super important argument why it should be one way or the other, we can still deviate from the poll result. |
Would be cool to see stats on how long people voting for either option looked at the poll. My guess is that most people think "fail" at first because, as @satya164 said, the object structure is built in such a different way, but then sway over to "pass" after thinking about the structure for a while. |
@jeysal Yep. I feel the same way. Lets wait and see :D |
Poll is 51% fail vs 19% pass, indicating that failing the assertion is likely more intuitive, as inaccurate as Twitter polling may be. |
@jeysal I agree with you that twitter poll can be quite inaccurate. I am waiting a review for now, but just to clarify your suggestion is following. expect(a).toEqual(b)
expect(a).not.toStrictEqual(b) Did I get it right? |
I think both Either way, we should definitely not introduce more differences between |
Finally, I followed link to Node.js code and studied its history for a few hours. Also I noticed a bullet point under Comparison details for
I feel pulled in 2 directions:
By the way, it looks like #7555 is like a test case from Node.js when it had become inconsistent. I think I misunderstood a quote from #7730 (comment)
Is that a statement of an intuitive (to us) requirement that node algorithm does not enforce? And is the change in most recent commit intended to enforce it: if (bMemoA !== undefined || bMemoB !== undefined) {
return bMemoA === bMemoB;
} In several pages of search results, the only relevant work that I could find was for Scheme: Although I keep tripping on examples where
|
{
x: {
- y: [Circular],
+ y: [Circular],
},
} |
The code called to my mind again before I turn my attention to more realistic test cases. The let result
if (lhsCircular.has(lhs)) {
result = lhsCircular.get(lhs) === rhsCircular.get(rhs)
? DEEP_EQUAL
: UNEQUAL
} else if (rhsCircular.has(rhs)) {
result = UNEQUAL
} else {
result = lhs.compare(rhs)
} That makes intuitive sense to me and the most recently committed change seems equivalent. Node.js ignores cycle to non-cycle and only compares cycle to cycle: const val2MemoA = memos.val1.get(val1);
if (val2MemoA !== undefined) {
const val2MemoB = memos.val2.get(val2);
if (val2MemoB !== undefined) {
return val2MemoA === val2MemoB;
}
} |
@grosto Have thought about this more slowly than oil paint drying on a canvas. I like your early intuition in #7555 (comment) with while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
// circular is equal to circular at same depth
// circular is not equal to non-circular
if (Object.is(aStack[length], a)) {
return Object.is(bStack[length], b);
} else if (Object.is(bStack[length], b)) {
return false;
}
} If testing circular to non-circular in only one direction is the root of the problem, then Node.js code has same problem (at the time of writing this comment) and makes To support filtering now and into the future, can you move the new tests into a The After so many changes in recent days: |
Sorry for being absent. I was traveling last week. First of all, thank you for all your input. Yes, you are right. If we are doing circular to non-circular testing for both arguments anyway, there is no need for copying node's implementation. I agree that just adding another check will be the best solution. I will update PR in several hours. |
Now that I think of it, you are free to rewrite as A note in case we ever rewrite the array stack as
I am sorry that issue and PR simmered in my mental slow cooker until all the water boiled away. This has been amazing start to 2019. People are opening issues about correctness of confusing parts of |
When I added temporary Can you adjust one of the existing cases (but not the one that corresponds to the issue) so the circularly referenced objects are one level deeper? |
aStack: any, | ||
bStack: any, | ||
customTesters: any, | ||
aStack: Array<unknown>, |
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.
better typing makes me happy! 🙏
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, he committed it better than he found it!
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.
better typing makes me happy! 🙏
Same here 🙌
a.x = a; | ||
const b = {}; | ||
b.x = b; | ||
expect(a).toEqual(b); |
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.
To make explicit guarantee that the comparison is symmetrical, do we want to double up on all the assertions?
Like the pair in the last test case.
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.
Fair point.
I will add another assertion to the test cases in which cyclic objects have the same shape.
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.
Let’s be explicit both for positive .toEqual
or negative .not.toEqual
There have been multiple new issues this week with inconsistent asymmetrical negative assertions in expect
package.
c.x = a; | ||
const d = {}; | ||
d.x = b; | ||
expect(c).toEqual(d); |
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.
Cool. I like it.
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.
It looks clear and correct. Deciding what is correct was the hardest work, heh?
Happy to hear that. "Just" several lines of code but thought me a lot. |
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.
Although I would have liked Jest to be the first to (IMO correctly) consider such structures equal, I can see why just the consistency with Node assert
is a good enough reason not to do it. Code LGTM!
@jeysal When we improve the reports when |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
fixes #7555
I have copied the node's implementation for comparing cyclic object. By which, two objects described in the issue are not considered equal.
Test plan
Added a new test case