Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

assert: deepEqual of two Sets with different content passes #13347

Closed
rmdm opened this issue May 31, 2017 · 10 comments
Closed

assert: deepEqual of two Sets with different content passes #13347

rmdm opened this issue May 31, 2017 · 10 comments
Labels
assert Issues and PRs related to the assert subsystem. confirmed-bug Issues with confirmed bugs.

Comments

@rmdm
Copy link
Contributor

rmdm commented May 31, 2017

  • 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.

@vsemozhetbyt
Copy link
Contributor

vsemozhetbyt commented May 31, 2017

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}]));

@refack refack added assert Issues and PRs related to the assert subsystem. confirmed-bug Issues with confirmed bugs. labels May 31, 2017
@vsemozhetbyt
Copy link
Contributor

сс @nodejs/testing

@vsemozhetbyt
Copy link
Contributor

Also cc @josephg due to #12142

@josephg
Copy link
Contributor

josephg commented May 31, 2017

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.

@josephg
Copy link
Contributor

josephg commented May 31, 2017

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]]))

@vsemozhetbyt
Copy link
Contributor

vsemozhetbyt commented May 31, 2017

(... 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}]))

@josephg
Copy link
Contributor

josephg commented May 31, 2017

.... 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.

josephg added a commit to josephg/node that referenced this issue Jun 3, 2017
This fixes a bug where deepEqual and deepStrictEqual would have
incorrect behaviour in sets and maps containing multiple equivalent
keys.

Fixes: nodejs#13347
Refs: nodejs#12142
@josephg
Copy link
Contributor

josephg commented Jun 3, 2017

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]]);

@refack refack closed this as completed in 7cddcc9 Jun 5, 2017
jasnell pushed a commit that referenced this issue Jun 7, 2017
This fixes a bug where deepEqual and deepStrictEqual would have
incorrect behaviour in sets and maps containing multiple equivalent
keys.

PR-URL: #13426
Fixes: #13347
Refs: #12142
Reviewed-By: Refael Ackermann <refack@gmail.com>
@escKeyStroke
Copy link

escKeyStroke commented Oct 17, 2017

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.

@josephg
Copy link
Contributor

josephg commented Oct 17, 2017

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())

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
assert Issues and PRs related to the assert subsystem. confirmed-bug Issues with confirmed bugs.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants