-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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: add deep equal check for more Error type #51805
assert: add deep equal check for more Error type #51805
Conversation
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.
This is already looking very promising!
The documentation still needs an update (we describe what properties we check in addition to enumerable ones) and I left improvement suggestions for edge cases.
lib/internal/util/comparisons.js
Outdated
return false; | ||
} | ||
if (val1 instanceof AggregateError && val2 instanceof AggregateError) { |
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 instanceof
check will work in most cases while it would not detect AggregateErrors
created in a different context/realm (e.g., by creating errors in a context created by vm.createContext()
). I believe it is fine to just always check for this property, in case it is not already an enumerable property.
This check would currently miss the case, if either side is an AggregateError
and the other is not. That could be addressed in a similar way as I suggested above about the enumerable property.
lib/internal/util/comparisons.js
Outdated
@@ -237,9 +238,15 @@ function innerDeepEqual(val1, val2, strict, memos) { | |||
// is otherwise identical. | |||
if ((!isNativeError(val2) && !(val2 instanceof Error)) || | |||
val1.message !== val2.message || | |||
val1.name !== val2.name) { | |||
val1.name !== val2.name || | |||
!innerDeepEqual(val1.cause, val2.cause, strict, memos)) { |
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.
This is definitely the right check as long the property is non-enumerable. If it is enumerable, we'd do the check twice and this could be costly and a lot of errors are created in the wild with a cause property set as an enumerable one.
In addition, we should also verify that the properties have the same enumerability. We don't yet do that for name
and message
, but that would be beneficial.
const cause1Enumerable = ObjectPrototypePropertyIsEnumerable(val1, 'cause');
if (cause1Enumerable !== ObjectPrototypePropertyIsEnumerable(val2, 'cause') ||
!cause1Enumerable &&
!innerDeepEqual(val1.cause, val2.cause, strict, memos)) {
return false;
}
test/parallel/test-assert-deep.js
Outdated
const e3 = new Error('e1'); | ||
const e4 = new AggregateError([e1, e2], 'Aggregate Error'); | ||
const e5 = new AggregateError([e1], 'Aggregate Error'); | ||
const e6 = new AggregateError([e1, e2], 'Aggregate Error'); |
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 (just a general suggestion without need to follow-up upon): the variable names could be changed in a way that it's clearer what they stand for. E.g., this one could be:
const e6 = new AggregateError([e1, e2], 'Aggregate Error'); | |
const e4duplicate = new AggregateError([e1, e2], 'Aggregate Error'); |
That way it would be easy to understand in the comparison that the entries are meant to be identical.
bd5f30d
to
bcf4e54
Compare
@BridgeAR Thanks for your suggestion! |
@BridgeAR, I have update based on Non-Enumerable properties on Error, PTAL feel free |
if ((message1Enumerable !== ObjectPrototypePropertyIsEnumerable(val2, 'message') || | ||
(!message1Enumerable && val1.message !== val2.message)) || | ||
(name1Enumerable !== ObjectPrototypePropertyIsEnumerable(val2, 'name') || | ||
(!name1Enumerable && val1.name !== val2.name)) || |
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.
As far as I can tell, this means now we only compare the name
and message
fields when they are enumerable, which specificaly opposite from the documentation, and is a breaking change...
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.
As far as I can tell, this means now we only compare the
name
andmessage
fields when they are enumerable, which specificaly opposite from the documentation, and is a breaking change...
FWIK, As mentioned in ECMAScript 2025, name
, message
, cause
and errors
are all non-enumerable, so in the judgement above, I just compared the attributes whether they have same enumerable
property, if it's same and they are non-enumerable
, then compare the attributes contents.
So if the attributes are enumerable
(for example in a custom Error
), the next check keyCheck
will check them, in the doc change, I have just added errors
, and causes
attributes, and didn't change name
and message
, I think its doesn't break the doc, have I missed something?
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.
If it was a breaking change, I would expect we would see test failures.
@legendecas PTAL |
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.
Would you mind adding an item to the change list in the YAML frontmatter as well? Such as:
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/51805
description: Error cause property is now compared as well.
--->
test/parallel/test-assert-deep.js
Outdated
const e2 = new Error('err', { cause: new Error('cause e2') }); | ||
assertNotDeepOrStrict(e1, e2, AssertionError); | ||
assertNotDeepOrStrict(e1, new Error('err'), AssertionError); | ||
assertDeepAndStrictEqual(e1, new Error('err', { cause: new Error('cause e1') }), 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.
assertDeepAndStrictEqual
only accepts two arguments.
assertDeepAndStrictEqual(e1, new Error('err', { cause: new Error('cause e1') }), AssertionError); | |
assertDeepAndStrictEqual(e1, new Error('err', { cause: new Error('cause e1') })); |
test/parallel/test-assert-deep.js
Outdated
const e4 = new AggregateError([e1], 'Aggregate Error'); | ||
assertNotDeepOrStrict(e1, e3, AssertionError); | ||
assertNotDeepOrStrict(e3, e4, AssertionError); | ||
assertDeepAndStrictEqual(e3, e3duplicate, 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.
assertDeepAndStrictEqual(e3, e3duplicate, AssertionError); | |
assertDeepAndStrictEqual(e3, e3duplicate); |
f93555c
to
5f48ab0
Compare
doc/api/assert.md
Outdated
* [`Error`][] names and messages are always compared, even if these are not | ||
enumerable properties. | ||
* [`Error`][] names, messages and causes are always compared, even if these are | ||
not enumerable properties. For `AggregateError`, non-enumerable property |
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 believe the actual behavior unconditionally compares the errors
property regardless of whether the error instance is an AggregateError
or not. This could be reworded as
* [`Error`][] names, messages, causes, and errors are always compared, even if these are not enumerable properties.
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.
Resolved. Thanks for the reminder
5f48ab0
to
7562ac6
Compare
Landed in c8a4f70 |
PR-URL: nodejs#51805 Fixes: nodejs#51793 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
PR-URL: nodejs#51805 Fixes: nodejs#51793 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
add deep equal check for the cases below:
cause
property:new Error('msg', { cause: xxx })
new AggregateError([err1, err2, ...], 'msg')
Fixes: #51793