Skip to content

Commit

Permalink
Allow cyclic references in .deepEqual.
Browse files Browse the repository at this point in the history
  • Loading branch information
strager committed Jul 22, 2010
1 parent cb97cdb commit 56dbd80
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 4 deletions.
20 changes: 16 additions & 4 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ assert.deepEqual = function deepEqual(actual, expected, message) {
}
};

function _deepEqual(actual, expected) {
function _deepEqual(actual, expected, actualVisitedObjects) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
Expand All @@ -148,7 +148,19 @@ function _deepEqual(actual, expected) {
// corresponding key, and an identical "prototype" property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected);
var i;

actualVisitedObjects = actualVisitedObjects || [ ];

for (i = actualVisitedObjects.length - 1; i >= 0; i--) {
if (actualVisitedObjects[i] === actual) {
return true;
}
}

actualVisitedObjects.push(actual);

return objEquiv(actual, expected, actualVisitedObjects);
}
}

Expand All @@ -160,7 +172,7 @@ function isArguments (object) {
return Object.prototype.toString.call(object) == '[object Arguments]';
}

function objEquiv (a, b) {
function objEquiv (a, b, actualVisitedObjects) {
if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
return false;
// an identical "prototype" property.
Expand Down Expand Up @@ -197,7 +209,7 @@ function objEquiv (a, b) {
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key] ))
if (!_deepEqual(a[key], b[key], actualVisitedObjects))
return false;
}
return true;
Expand Down
51 changes: 51 additions & 0 deletions test/simple/test-assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,57 @@ assert.throws(makeBlock(a.deepEqual, nb1, nb2), a.AssertionError);
// String literal + object blew up my implementation...
assert.throws(makeBlock(a.deepEqual, 'a', {}), a.AssertionError);

// Cyclic reference
var testA = { };
var testB = { foo: testA };
testA.bar = testB;

var testC = { };
var testD = { bar: testC };
testC.foo = testD;

assert.doesNotThrow(makeBlock(a.deepEqual, testA, testA), a.AssertionError);
assert.doesNotThrow(makeBlock(a.deepEqual, testA, testD), a.AssertionError);
assert.doesNotThrow(makeBlock(a.deepEqual, testD, testA), a.AssertionError);
assert.throws(makeBlock(a.deepEqual, testA, testC), a.AssertionError);
assert.throws(makeBlock(a.deepEqual, testB, testD), a.AssertionError);
assert.throws(makeBlock(a.deepEqual, testC, testA), a.AssertionError);
assert.throws(makeBlock(a.deepEqual, testD, testB), a.AssertionError);

var source, dest;
testA = [ [ 1 ], [ 2 ], [ 3 ] ];
testB = [ [ 1 ], [ 2 ], [ 3 ] ];
testC = [ [ 1 ], [ 2 ], [ 3 ] ];

for (dest = 2; dest >= 0; dest--) {
for (source = 2; source >= 0; source--) {
testA[dest].push(testA[source]);
}
}

for (dest = 2; dest >= 0; dest--) {
for (source = 2; source >= 0; source--) {
testB[dest].push(testB[source]);
}
}

// testC has different ordering
for (dest = 2; dest >= 0; dest--) {
for (source = 0; source < 3; source++) {
testC[dest].push(testC[source]);
}
}

assert.doesNotThrow(makeBlock(a.deepEqual, testA, testA), a.AssertionError);
assert.doesNotThrow(makeBlock(a.deepEqual, testA, testB), a.AssertionError);
assert.doesNotThrow(makeBlock(a.deepEqual, testB, testA), a.AssertionError);
assert.doesNotThrow(makeBlock(a.deepEqual, testB, testB), a.AssertionError);
assert.doesNotThrow(makeBlock(a.deepEqual, testC, testC), a.AssertionError);
assert.throws(makeBlock(a.deepEqual, testA, testC), a.AssertionError);
assert.throws(makeBlock(a.deepEqual, testB, testC), a.AssertionError);
assert.throws(makeBlock(a.deepEqual, testC, testA), a.AssertionError);
assert.throws(makeBlock(a.deepEqual, testC, testB), a.AssertionError);

// Testing the throwing
function thrower (errorConstructor){
throw new errorConstructor('test');
Expand Down

0 comments on commit 56dbd80

Please sign in to comment.