Node: assert: deepEqual of two Sets with different content passes

Created on 31 May 2017  路  10Comments  路  Source: nodejs/node

  • Version: v8.0.0
  • Platform: Linux 3.16.0-4-amd64 SMP Debian 3.16.39-1 (2016-12-30) x86_64 GNU/Linux
  • Subsystem: assert
assert.deepEqual(new Set([{a: 1}, {a: 1}]), new Set([{a: 1}, {a: 2}]))
assert.deepStrictEqual(new Set([{a: 1}, {a: 1}]), new Set([{a: 1}, {a: 2}]))

Both of these checks passes, while I would expect them to fail.

assert confirmed-bug

All 10 comments

I can reproduce.
This fails:

assert.deepEqual(new Set([{a: 1}, {a: 1}]), new Set([{b: 1}, {b: 2}]));
assert.deepStrictEqual(new Set([{a: 1}, {a: 1}]), new Set([{b: 1}, {b: 2}]));

This also fails:

assert.deepEqual(new Set([{a: 1}]), new Set([{a: 2}]));
assert.deepStrictEqual(new Set([{a: 1}]), new Set([{a: 2}]));

褋褋 @nodejs/testing

Errr, yep this is a bug.

assert.deepStrictEqual(new Set([{a: 1}, {a: 1}]), new Set([{a: 1}, {a: 2}]))

There's two semantics going on here:

  1. In a set, these objects are different: {a:1} and {a:1} because they aren't references to the same object.
  2. In deep[Strict]Equal, they are the same.

So you've made a set with two different objects that are considered identical by deepEqual. As far as deepEqual should be concerned, is this equivalent to a set with only one {a:1} in it? Its sort of weird.

Anyway, after a long discussion deep equality comparison currently works by doing this:

  1. Assert the sets have identical .size
  2. For each item in set 1, find an equivalent item in set 2

The .size comparison uses the semantics of sets - that is, internal reference equality. The pairwise item comparisons uses the semantics of deepEqual / deepStrictEqual. And hence the bug.

~We could fix it by changing the code to do it both ways around:~ (Edit: This is also buggy - see my comment below)

  1. Assert the sets have identical .size
  2. For each item in set 1, find an equivalent item in set 2
  3. For each item in set 2, find an equivalent item in set 1

(... although then step 1 isn't needed anymore, and might cause more bugs?)

Alternately, we could track which items of set 2 have already been consumed by the matching. So:

  1. Assert the sets have identical .size
  2. For each item in set 1, find an equivalent item in set 2 that hasn't already been used

.. Which I think is correct.

And, maps have the equivalent logic & same bug:

assert.deepEqual(new Map([[{x:1}, 5], [{x:1}, 5]]), new Map([[{x:1}, 5], [{x:2}, 5]]))
assert.deepStrictEqual(new Map([[{x:1}, 5], [{x:1}, 5]]), new Map([[{x:1}, 5], [{x:2}, 5]]))

(... although then step 1 isn't needed anymore, and might cause more bugs?)

Is it still needed with this approach to prevent this from being equal?

assert.deepStrictEqual(new Set([{a: 1}, {a: 1}]), new Set([{a: 1}]))

.... Er, yeah, sure is @vsemozhetbyt :)

Actually the first method would consider these sets to be equal:

new Set([{a:1}, {a:1}, {a:2}])
new Set([{a:1}, {a:2}, {a:2}])

So we should fix this using the second method.

This also fails to throw, due to the same bug:

assert.deepEqual(new Set([3, '3']), new Set([3, 4]));
// and
assert.deepEqual(new Map([[3, 0], ['3', 0]]), new Map([[3, 0], [4, 0]]);

A simpler case

... and heads up by a bro from lodash.js
(version 6.11.4, Windows x64)

assert.deepEqual( new Map([['x', 'y']]),
                  new Map([['y', 'x']]) ); // Doesn't throw

I assert that this is fixed in v8.7.0.

Primitive support for map and set equality wasn't added until node 8. In node 6 all maps and all sets are considered equivalent. These also didn't throw in node 7 and below:

assert.deepEqual(new Map(), new Map())
assert.deepEqual(new Map(), new Set())
Was this page helpful?
0 / 5 - 0 ratings

Related issues

fanjunzhi picture fanjunzhi  路  3Comments

stevenvachon picture stevenvachon  路  3Comments

dfahlander picture dfahlander  路  3Comments

jmichae3 picture jmichae3  路  3Comments

addaleax picture addaleax  路  3Comments