Skip to content
New issue

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

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

Already on GitHub? Sign in 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 <[email protected]>
@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())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in 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