-
Notifications
You must be signed in to change notification settings - Fork 30k
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
assert: use same value equal for deepStrictEqual NaN #15036
Conversation
doc/api/assert.md
Outdated
comparison. (Which means they are free of the [caveats][]). | ||
1. Primitive values besides NaN are compared using the [Strict Equality | ||
Comparison][] ( `===` ). Set and Map values, Map keys and NaN are compared | ||
using the [SameValueZero][] comparison (Which means they are free of the |
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.
Nit: Which
-> which
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.
Addressed
Maybe this line with exposed markdown syntax in JS example worth fixing? It can be confusing a bit. |
@vsemozhetbyt thx, fixed |
test/parallel/test-assert-deep.js
Outdated
@@ -466,4 +466,10 @@ assertOnlyDeepEqual( | |||
assertDeepAndStrictEqual(m3, m4); | |||
} | |||
|
|||
// Handle NaN | |||
{ | |||
assert.throws(() => assert.deepEqual(NaN, NaN), assert.AssertionError); |
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.
Nit: Leaving out the braces around the arrow function adds an implicit return. Since the returned value is not used, I prefer to add braces. Feel free to ignore, but my two cents on it.
test/parallel/test-assert-deep.js
Outdated
// Handle NaN | ||
{ | ||
assert.throws(() => assert.deepEqual(NaN, NaN), assert.AssertionError); | ||
assert.doesNotThrow(() => assert.deepStrictEqual(NaN, NaN)); |
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.
Since tests are a form of documentation, maybe add an additional test case or two that demonstrates the utility a bit more:
assert.doesNotThrow(() => { assert.deepStrictEqual({ a: NaN }, { a: NaN });
assert.doesNotThrow(() => { assert.deepStrictEqual( [ 1, 2, NaN, 4 ], [ 1, 2, NaN, 4 ]);
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.
LGTM (with or without my comments addressed) if CI passes
I updated the tests according to your suggestions @Trott |
doc/api/assert.md
Outdated
@@ -102,6 +102,9 @@ parameter is undefined, a default error message is assigned. | |||
<!-- YAML | |||
added: v1.2.0 | |||
changes: | |||
- version: REPLACEME | |||
pr-url: https://github.com/nodejs/node/pull/12142 |
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.
Is this the proper link?
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.
Good catch 😃
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.
More request comment, than request change.
test/parallel/test-assert-deep.js
Outdated
@@ -466,4 +466,11 @@ assertOnlyDeepEqual( | |||
assertDeepAndStrictEqual(m3, m4); | |||
} | |||
|
|||
// Handle NaN | |||
assert.throws(() => { assert.deepEqual(NaN, NaN); }, assert.AssertionError); |
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.
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.
That is the bad part about the wording with strict
. The strict stands for more exact in this case. deepEqual
is actually more like a looksSimilar
and it actually fails at seeing the similarity in this case even though it exists. I can add that check to deepEqual as well but I think it would be weird to have it there (even though I would call deepEqual broken anyway - we actually also use a own lint rule to prevent usage of 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.
Actually that is IMHO a great idea, create a better named superset assertion. That would also make this change semver-minor.
sameValues
might be a better name (that way neither the word "strict" nor "deep" are necessary)
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.
I think this is absolutely not the right thing to do. And as I tried to explain earlier deepStrictEqual
is not strictly a superset of deepEqual
because it was just a poor naming from the beginning on. Now we have to live with that naming. Most people do for example not know that NaN
is not equal to itself when compared with strict equality
. And they also do not know about the other existing equality comparisons in JS. They mostly do not even know how abstract equality
is defined - at least that is what my experience tells me.
The comparisons in general are well described here. Adding a new API would not help anyone and would instead create more confusion.
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.
I agree that assert.deepStrictEqual(NaN,NaN)
makes sense intuitively.
But ECMAScript abstract equal
is "broken" in this sense ((NaN == NaN) === false
) and strict equal
is broken ((NaN === NaN) === false
) is the exact same way.
So IMHO we should be consistent with the standard if we keep using the term equal
in our end-point names.
That's why I think breaking away forfrom legacy with a same
end-point might be a good idea.
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.
The deepEqual and deepStrictEqual functions have much more then just the "basic" comparison and therefore do not correspond to these properly. I would love if that would be the only distinction because in that case deepEqual
would not be so badly broken but that is not the case and it will not change either.
Adding another random name makes the issue not less an issue but just added more problems.
if (actual === null || expected === null || | ||
typeof actual !== 'object' || typeof expected !== 'object') { | ||
if (typeof actual !== 'object') { | ||
return typeof actual === 'number' && Number.isNaN(actual) && |
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.
I think typeof actual === 'number'
can be removed, Number.isNaN(actual)
is sufficient.
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.
That is correct but it is faster for all other types to check for number first and if it is a number it is not a real penalty because that check is super fast.
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.
Does it makes sense to do the same for expected
then?
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 could but I feel like that is overdoing it as it would only apply for the case where Number.isNaN(actual)
, especially as I would expect expected
to also be NaN in that case more often then not being of type number.
This will actually also fix a issue in a N-API test case that expects NaN to be equal to each other and they tested it by using deepStrictEqual (the error will only show up in #15050).
I personally think this could be counted as a bug fix instead of a semver-major because I highly doubt that anyone relies on |
Changing this behavior could cause tests to succeed erroneously where two calculations are expected to come up with the same number but a bug in the code causes them both to come up with I'd prefer it be treated as |
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'll need to deprecate deepEqual
eventually
IMHO it's a bug fix of the ad hoc spec (I see the current behavior of |
@refack No, it's not. I actually considered implementing
|
doc/api/assert.md
Outdated
( `===` ). Set values and Map keys are compared using the [SameValueZero][] | ||
comparison. (Which means they are free of the [caveats][]). | ||
1. Primitive values besides NaN are compared using the [Strict Equality | ||
Comparison][] ( `===` ). Set and Map values, Map keys and NaN are compared |
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 usually use monospacing for constants such as NaN
in the docs.
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.
Addressed
@BridgeAR pointed me to that spec as well. IMHO that spec should not have been implemented verbatim in JS, but that's done (BTW: If I read this correctly then My point was that if |
But in that case the test should not have existed yet as it would have otherwise failed and the bug got hopefully fixed? So it is only something for the future, right? |
I rebased due to conflicts and addressed the monospacing. |
I'm thinking of regressions where a bug is introduced while refactoring or something like that. Might be unlikely as you'd need to mess up two calculations that way simultaneously, I suppose. |
Rebased due to conflicts. I also fixed a documentation bug from a former commit where the PR number was wrong. @Trott I am not sure I can follow. If I read your comment correct you would rather keep it as semver-major, right? |
@BridgeAR I'd prefer it be treated as semver-major out of caution but it's a very mild preference. If no one else agrees, I'm fine with my mild preference being disregarded. :-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.
LGTM as semver-major
PR-URL: #15036 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Comparing NaN will not throw anymore. PR-URL: #15036 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
PR-URL: nodejs/node#15036 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Comparing NaN will not throw anymore. PR-URL: nodejs/node#15036 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
I think it makes sense to further improve assert a bit and to accept NaN as well.
I also fixed the documentation a bit by adding missing changelogs to the assert docs.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
assert, doc