diff --git a/doc/api/assert.md b/doc/api/assert.md index 70e13f3b87760c..c16f37bf3809e0 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -125,12 +125,13 @@ changes: * `expected` {any} * `message` {any} -Generally identical to `assert.deepEqual()` with two exceptions: +Generally identical to `assert.deepEqual()` with three exceptions: 1. Primitive values are compared using the [Strict Equality Comparison][] ( `===` ). 2. [`[[Prototype]]`][prototype-spec] of objects are compared using the [Strict Equality Comparison][] too. +3. [Type tags][Object.prototype.toString()] of objects should be the same. ```js const assert = require('assert'); @@ -141,6 +142,25 @@ assert.deepEqual({a: 1}, {a: '1'}); assert.deepStrictEqual({a: 1}, {a: '1'}); // AssertionError: { a: 1 } deepStrictEqual { a: '1' } // because 1 !== '1' using strict equality + +// The following objects don't have owned properties +const date = new Date(); +const object = {}; +const fakeDate = {}; + +Object.setPrototypeOf(fakeDate, Date.prototype); + +assert.deepEqual(object, fakeDate); +// OK, doesn't check [[Prototype]] +assert.deepStrictEqual(object, fakeDate); +// AssertionError: {} deepStrictEqual Date {} +// Different [[Prototype]] + +assert.deepEqual(date, fakeDate); +// OK, doesn't check type tags +assert.deepStrictEqual(date, fakeDate); +// AssertionError: 2017-03-11T14:25:31.849Z deepStrictEqual Date {} +// Different type tags ``` If the values are not equal, an `AssertionError` is thrown with a `message` diff --git a/lib/assert.js b/lib/assert.js index 3183784dbfc42d..df575c0f169b22 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -200,6 +200,10 @@ function _deepEqual(actual, expected, strict, memos) { if (Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) { return false; } + + if (actualTag !== expectedTag) { + return false; + } } // Do fast checks for builtin types. diff --git a/test/parallel/test-assert-checktag.js b/test/parallel/test-assert-checktag.js new file mode 100644 index 00000000000000..ae409c7250350e --- /dev/null +++ b/test/parallel/test-assert-checktag.js @@ -0,0 +1,61 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const util = require('util'); + +// Template tag function turning an error message into a RegExp +// for assert.throws() +function re(literals, ...values) { + let result = literals[0]; + for (const [i, value] of values.entries()) { + const str = util.inspect(value); + // Need to escape special characters. + result += str.replace(/[\\^$.*+?()[\]{}|=!<>:-]/g, '\\$&'); + result += literals[i + 1]; + } + return new RegExp('^AssertionError: ' + result + '$'); +} + +// Turn off no-restricted-properties because we are testing deepEqual! +/* eslint-disable no-restricted-properties */ + +// See https://github.com/nodejs/node/issues/10258 +{ + const date = new Date('2016'); + function FakeDate() {} + FakeDate.prototype = Date.prototype; + const fake = new FakeDate(); + + assert.doesNotThrow(() => assert.deepEqual(date, fake)); + assert.doesNotThrow(() => assert.deepEqual(fake, date)); + + // For deepStrictEqual we check the runtime type, + // then reveal the fakeness of the fake date + assert.throws(() => assert.deepStrictEqual(date, fake), + re`${date} deepStrictEqual Date {}`); + assert.throws(() => assert.deepStrictEqual(fake, date), + re`Date {} deepStrictEqual ${date}`); +} + +{ // At the moment global has its own type tag + const fakeGlobal = {}; + Object.setPrototypeOf(fakeGlobal, Object.getPrototypeOf(global)); + for (const prop of Object.keys(global)) { + fakeGlobal[prop] = global[prop]; + } + assert.doesNotThrow(() => assert.deepEqual(fakeGlobal, global)); + // Message will be truncated anyway, don't validate + assert.throws(() => assert.deepStrictEqual(fakeGlobal, global)); +} + +{ // At the moment process has its own type tag + const fakeProcess = {}; + Object.setPrototypeOf(fakeProcess, Object.getPrototypeOf(process)); + for (const prop of Object.keys(process)) { + fakeProcess[prop] = process[prop]; + } + assert.doesNotThrow(() => assert.deepEqual(fakeProcess, process)); + // Message will be truncated anyway, don't validate + assert.throws(() => assert.deepStrictEqual(fakeProcess, process)); +} +/* eslint-enable */