Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

assert.deepEqual doing inadequate comparison #7161

Closed
@joshwilsdon

Description

@joshwilsdon

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions