Browse Source

assert: allow circular references

assert.deepEqual() and assert.deepStrictEqual() will no longer throw a
RangeError if passed objects with circular references.

PR-URL: https://github.com/nodejs/node/pull/6432
Fixes: https://github.com/nodejs/node/issues/6416
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
process-exit-stdio-flushing
Rich Trott 9 years ago
parent
commit
d3aafd02ef
  1. 21
      lib/assert.js
  2. 32
      test/parallel/test-assert.js

21
lib/assert.js

@ -143,7 +143,7 @@ assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
} }
}; };
function _deepEqual(actual, expected, strict) { function _deepEqual(actual, expected, strict, memos) {
// 7.1. All identical values are equivalent, as determined by ===. // 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) { if (actual === expected) {
return true; return true;
@ -191,7 +191,19 @@ function _deepEqual(actual, expected, strict) {
// corresponding key, and an identical 'prototype' property. Note: this // corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays. // accounts for both named and indexed properties on Arrays.
} else { } else {
return objEquiv(actual, expected, strict); memos = memos || {actual: [], expected: []};
const actualIndex = memos.actual.indexOf(actual);
if (actualIndex !== -1) {
if (actualIndex === memos.expected.indexOf(expected)) {
return true;
}
}
memos.actual.push(actual);
memos.expected.push(expected);
return objEquiv(actual, expected, strict, memos);
} }
} }
@ -199,7 +211,7 @@ function isArguments(object) {
return Object.prototype.toString.call(object) == '[object Arguments]'; return Object.prototype.toString.call(object) == '[object Arguments]';
} }
function objEquiv(a, b, strict) { function objEquiv(a, b, strict, actualVisitedObjects) {
if (a === null || a === undefined || b === null || b === undefined) if (a === null || a === undefined || b === null || b === undefined)
return false; return false;
// if one is a primitive, the other must be same // if one is a primitive, the other must be same
@ -235,7 +247,8 @@ function objEquiv(a, b, strict) {
//~~~possibly expensive deep test //~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) { for (i = ka.length - 1; i >= 0; i--) {
key = ka[i]; key = ka[i];
if (!_deepEqual(a[key], b[key], strict)) return false; if (!_deepEqual(a[key], b[key], strict, actualVisitedObjects))
return false;
} }
return true; return true;
} }

32
test/parallel/test-assert.js

@ -381,25 +381,33 @@ try {
assert.ok(threw); assert.ok(threw);
// GH-207. Make sure deepEqual doesn't loop forever on circular refs // https://github.com/nodejs/node/issues/6416
var b = {}; // Make sure circular refs don't throw.
b.b = b; {
const b = {};
b.b = b;
var c = {}; const c = {};
c.b = c; c.b = c;
var gotError = false; a.doesNotThrow(makeBlock(a.deepEqual, b, c));
try { a.doesNotThrow(makeBlock(a.deepStrictEqual, b, c));
assert.deepEqual(b, c);
} catch (e) {
gotError = true;
}
const d = {};
d.a = 1;
d.b = d;
const e = {};
e.a = 1;
e.b = e.a;
a.throws(makeBlock(a.deepEqual, d, e), /AssertionError/);
a.throws(makeBlock(a.deepStrictEqual, d, e), /AssertionError/);
}
// GH-7178. Ensure reflexivity of deepEqual with `arguments` objects. // GH-7178. Ensure reflexivity of deepEqual with `arguments` objects.
var args = (function() { return arguments; })(); var args = (function() { return arguments; })();
a.throws(makeBlock(a.deepEqual, [], args)); a.throws(makeBlock(a.deepEqual, [], args));
a.throws(makeBlock(a.deepEqual, args, [])); a.throws(makeBlock(a.deepEqual, args, []));
assert.ok(gotError);
var circular = {y: 1}; var circular = {y: 1};

Loading…
Cancel
Save