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

assert: handle errors properly with deep*Equal #15001

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions doc/api/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ Only [enumerable "own" properties][] are considered. The
non-enumerable properties — for such checks, consider using
[`assert.deepStrictEqual()`][] instead. This can lead to some
potentially surprising results. For example, the following example does not
throw an `AssertionError` because the properties on the [`Error`][] object are
throw an `AssertionError` because the properties on the [`RegExp`][] object are
not enumerable:

```js
// WARNING: This does not throw an AssertionError!
assert.deepEqual(Error('a'), Error('b'));
assert.deepEqual(/a/gi, new Date());
```

An exception is made for [`Map`][] and [`Set`][]. Maps and Sets have their
Expand Down Expand Up @@ -104,6 +104,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
description: Error names and messages are now properly compared
- version: v8.0.0
pr-url: https://github.com/nodejs/node/pull/12142
description: Set and Map content is also compared
Expand Down
11 changes: 11 additions & 0 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ function strictDeepEqual(actual, expected) {
if (!areSimilarRegExps(actual, expected)) {
return false;
}
} else if (actualTag === '[object Error]') {
// Do not compare the stack as it might differ even though the error itself
// is otherwise identical. The non-enumerable name should be identical as
// the prototype is also identical. Otherwise this is caught later on.
if (actual.message !== expected.message) {
return false;
}
} else if (!isFloatTypedArrayTag(actualTag) && ArrayBuffer.isView(actual)) {
if (!areSimilarTypedArrays(actual, expected)) {
return false;
Expand Down Expand Up @@ -230,6 +237,10 @@ function looseDeepEqual(actual, expected) {
if (isRegExp(actual) && isRegExp(expected)) {
return areSimilarRegExps(actual, expected);
}
if (actual instanceof Error && expected instanceof Error) {
if (actual.message !== expected.message || actual.name !== expected.name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you compare code as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

code will be compared if it is set by the user as a non enumerable property. This is only intended for the non enumerable properties message and name from the default Error classes and not for userland sub-classing.

Copy link
Contributor

@refack refack Aug 31, 2017

Choose a reason for hiding this comment

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

Ahh Ok. The keys of expected are the assertion demand.
(I was thinking of the case of trying to assert a NodeError where .code comes from the prototype)
So will this be an effective check?

assert.deepStrictEqual(someNodeError, Object.assign(new Error(msg), {code: ERR_CODE}));

Copy link
Member Author

Choose a reason for hiding this comment

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

This would fail because the left prototype is not the same as the right one.

return false;
}
const actualTag = objectToString(actual);
const expectedTag = objectToString(expected);
if (actualTag === expectedTag) {
Expand Down
28 changes: 20 additions & 8 deletions test/parallel/test-assert-deep.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,23 @@ function assertDeepAndStrictEqual(a, b) {
assert.deepStrictEqual(b, a);
}

function assertNotDeepOrStrict(a, b) {
assert.throws(() => assert.deepEqual(a, b), re`${a} deepEqual ${b}`);
assert.throws(() => assert.deepStrictEqual(a, b),
function assertNotDeepOrStrict(a, b, err) {
assert.throws(() => assert.deepEqual(a, b), err || re`${a} deepEqual ${b}`);
assert.throws(() => assert.deepStrictEqual(a, b), err ||
re`${a} deepStrictEqual ${b}`);

assert.throws(() => assert.deepEqual(b, a), re`${b} deepEqual ${a}`);
assert.throws(() => assert.deepStrictEqual(b, a),
assert.throws(() => assert.deepEqual(b, a), err || re`${b} deepEqual ${a}`);
assert.throws(() => assert.deepStrictEqual(b, a), err ||
re`${b} deepStrictEqual ${a}`);
}

function assertOnlyDeepEqual(a, b) {
function assertOnlyDeepEqual(a, b, err) {
assert.doesNotThrow(() => assert.deepEqual(a, b));
assert.throws(() => assert.deepStrictEqual(a, b),
assert.throws(() => assert.deepStrictEqual(a, b), err ||
re`${a} deepStrictEqual ${b}`);

assert.doesNotThrow(() => assert.deepEqual(b, a));
assert.throws(() => assert.deepStrictEqual(b, a),
assert.throws(() => assert.deepStrictEqual(b, a), err ||
re`${b} deepStrictEqual ${a}`);
}

Expand Down Expand Up @@ -469,4 +469,16 @@ assertOnlyDeepEqual(
assertDeepAndStrictEqual([1, , , 3], [1, , , 3]);
assertOnlyDeepEqual([1, , , 3], [1, , , 3, , , ]);

// Handle different error messages
{
const err1 = new Error('foo1');
const err2 = new Error('foo2');
const err3 = new TypeError('foo1');
assertNotDeepOrStrict(err1, err2, assert.AssertionError);
assertNotDeepOrStrict(err1, err3, assert.AssertionError);
// TODO: evaluate if this should throw or not. The same applies for RegExp
// Date and any object that has the same keys but not the same prototype.
assertOnlyDeepEqual(err1, {}, assert.AssertionError);
}

/* eslint-enable */