assert.deepEqual doing inadequate comparison #7161
Description
I was bitten originally by what I believe to be a bug in nodeunit (I will create a pull request there if behavior is changed in node). Looking further nodeunit got this because it appears to have copied the assert.js code from node. The issue I specifically ran into is that the following sorts of values are considered equal (tested on 0.10.24 on OS X):
> var assert = require('assert');
undefined
> assert.deepEqual('1', 1);
undefined
> assert.deepEqual('42', 42);
undefined
> assert.deepEqual(1, true);
undefined
> assert.deepEqual(0, false);
undefined
> assert.deepEqual({a: {b: [0, 1]}}, {a: {b: ["0", "1"]}});
undefined
>
I then found that Mozilla had this problem previously with a Utils.deepEquals function as described in https://bugzilla.mozilla.org/show_bug.cgi?id=493363 so I modified the same test code from that bug, to work with node and adding 2 additional tests:
var assert = require('./assert');
d1 = [NaN, undefined, null, true, false, Infinity, 0, 1, "a", "b", {a: 1}, {a: "a"}, [{a: 1}], [{a: true}], {a: 1, b: 2}, [1, 2], [1, 2, 3], {a: "1"}, {a: "1", b: "2"}];
d2 = [NaN, undefined, null, true, false, Infinity, 0, 1, "a", "b", {a: 1}, {a: "a"}, [{a: 1}], [{a: true}], {a: 1, b: 2}, [1, 2], [1, 2, 3], {a: "1"}, {a: "1", b: "2"}];
d1.forEach(function(a) {
var o = [];
d2.forEach(function(b) {
try {
assert.deepEqual(a,b);
// no error
o.push("1");
} catch (e) {
o.push(" ");
}
// o.push(assert.deepEqual(a,b) ? 1 : " ");
});
console.log(o.concat([typeof (a), a, JSON.stringify([a])]).join(" "));
});
and found output:
number NaN [null]
1 undefined [null]
1 object [null]
1 1 boolean true [true]
1 1 boolean false [false]
1 number Infinity [null]
1 1 number 0 [0]
1 1 number 1 [1]
1 string a ["a"]
1 string b ["b"]
1 1 object [object Object] [{"a":1}]
1 object [object Object] [{"a":"a"}]
1 1 object [object Object] [[{"a":1}]]
1 1 object [object Object] [[{"a":true}]]
1 1 object [object Object] [{"a":1,"b":2}]
1 object 1,2 [[1,2]]
1 object 1,2,3 [[1,2,3]]
1 1 object [object Object] [{"a":"1"}]
1 1 object [object Object] [{"a":"1","b":"2"}]
which shows several things matching only because we're doing == instead of ===. Taking the assert.js from the v0.10 branch and applying this patch:
--- assert.js.orig 2014-02-20 23:00:58.000000000 -0800
+++ assert.js 2014-02-20 23:02:51.000000000 -0800
@@ -169,9 +169,9 @@
actual.ignoreCase === expected.ignoreCase;
// 7.4. Other pairs that do not both pass typeof value == 'object',
- // equivalence is determined by ==.
+ // equivalence is determined by ===.
} else if (typeof actual != 'object' && typeof expected != 'object') {
- return actual == expected;
+ return actual === expected;
// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
the results become:
number NaN [null]
1 undefined [null]
1 object [null]
1 boolean true [true]
1 boolean false [false]
1 number Infinity [null]
1 number 0 [0]
1 number 1 [1]
1 string a ["a"]
1 string b ["b"]
1 object [object Object] [{"a":1}]
1 object [object Object] [{"a":"a"}]
1 object [object Object] [[{"a":1}]]
1 object [object Object] [[{"a":true}]]
1 object [object Object] [{"a":1,"b":2}]
1 object 1,2 [[1,2]]
1 object 1,2,3 [[1,2,3]]
1 object [object Object] [{"a":"1"}]
1 object [object Object] [{"a":"1","b":"2"}]
which seems correct. Back to my original examples after applying this change, they also fail as I'd expect:
> var assert = require('./assert.js');
undefined
> assert.deepEqual('1', 1);
AssertionError: "1" deepEqual 1
at repl:1:8
at REPLServer.self.eval (repl.js:110:21)
at repl.js:249:20
at REPLServer.self.eval (repl.js:122:7)
at Interface.<anonymous> (repl.js:239:12)
at Interface.EventEmitter.emit (events.js:95:17)
at Interface._onLine (readline.js:202:10)
at Interface._line (readline.js:531:8)
at Interface._ttyWrite (readline.js:760:14)
at ReadStream.onkeypress (readline.js:99:10)
> assert.deepEqual('42', 42);
AssertionError: "42" deepEqual 42
at repl:1:8
at REPLServer.self.eval (repl.js:110:21)
at repl.js:249:20
at REPLServer.self.eval (repl.js:122:7)
at Interface.<anonymous> (repl.js:239:12)
at Interface.EventEmitter.emit (events.js:95:17)
at Interface._onLine (readline.js:202:10)
at Interface._line (readline.js:531:8)
at Interface._ttyWrite (readline.js:760:14)
at ReadStream.onkeypress (readline.js:99:10)
> assert.deepEqual(1, true);
AssertionError: 1 deepEqual true
at repl:1:8
at REPLServer.self.eval (repl.js:110:21)
at repl.js:249:20
at REPLServer.self.eval (repl.js:122:7)
at Interface.<anonymous> (repl.js:239:12)
at Interface.EventEmitter.emit (events.js:95:17)
at Interface._onLine (readline.js:202:10)
at Interface._line (readline.js:531:8)
at Interface._ttyWrite (readline.js:760:14)
at ReadStream.onkeypress (readline.js:99:10)
> assert.deepEqual(0, false);
AssertionError: 0 deepEqual false
at repl:1:8
at REPLServer.self.eval (repl.js:110:21)
at repl.js:249:20
at REPLServer.self.eval (repl.js:122:7)
at Interface.<anonymous> (repl.js:239:12)
at Interface.EventEmitter.emit (events.js:95:17)
at Interface._onLine (readline.js:202:10)
at Interface._line (readline.js:531:8)
at Interface._ttyWrite (readline.js:760:14)
at ReadStream.onkeypress (readline.js:99:10)
> assert.deepEqual({a: {b: [0, 1]}}, {a: {b: ["0", "1"]}});
AssertionError: {"a":{"b":[0,1]}} deepEqual {"a":{"b":["0","1"]}}
at repl:1:8
at REPLServer.self.eval (repl.js:110:21)
at repl.js:249:20
at REPLServer.self.eval (repl.js:122:7)
at Interface.<anonymous> (repl.js:239:12)
at Interface.EventEmitter.emit (events.js:95:17)
at Interface._onLine (readline.js:202:10)
at Interface._line (readline.js:531:8)
at Interface._ttyWrite (readline.js:760:14)
at ReadStream.onkeypress (readline.js:99:10)
>
Any chance that this could get applied? Looking at master, it seems the issue still exists there as well though the typeof check has changed to using util.isObject instead so the patch won't apply as-is. It appears to be the same 1 character (and comment) fix there though.