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: add support for Set and Map #2315

Closed
wants to merge 1 commit into from

Conversation

evanlucas
Copy link
Contributor

Previously, assert.deepEqual used on maps and sets was not functioning
correctly.

This change adds support for correctly comparing Maps and Sets.

For Maps, comparison is done by iterating over each item in both Maps
and verifying that the key exists and the value is the same (depending
on whether strict is applied).

For Sets, each item is iterated in each Set and checked if it exists in
the other Set.

For both Maps and Sets, the size of both being compared is checked as
well.

Related: #2309

@evanlucas evanlucas added the assert Issues and PRs related to the assert subsystem. label Aug 6, 2015

function compareSetItems(a, b, strict) {
for (let item of a) {
if (!b.has(item))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should check the same index for sets, not sure about maps.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Index? You mean the order?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking sets are unordered, should we really worry about it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I agree with @Fishrock123 here. Order should probably be checked as well IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, sets do seem to be kind of unordered, you can't just "get" an index: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking sets are unordered

Strictly correct, but note that unlike ES5 Objects where iteration is in "whatever, probably insertion" order, iteration over Sets and Maps is defined in ES2015 to occur in "insertion order". Despite this ordered iteration, one could consider the underlying data represented by the Set/Map datastructure to be unordered though.

While verifying order may be more strictly "equal", I disagree that this is very useful in practice though. Ignoring the spec, consider whether deepEqual would be more or less useful if it also verified the key order for objects. IMO, for common use cases, verification of the iteration order is not important. If you do want to additionally verify the ordering, it's trivial enough to do: convert the Set/Map.keys() to arrays and deepEqual the arrays.

Consider then if deepEqual does check order and one wants to ignore the iteration order one will need to create an array of the keys/values, sort it somehow (depends on data), then use this to create a new Map/Set. Checking iteration order would make the IMO common usecase more verbose.

It should check the same index for sets, not sure about maps.

deepEqual over Sets and Maps should exhibit the same behaviour with regard to order.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me.

if (!compareMapKeys(a, b, strict))
return false;

return compareMapKeys(b, a, strict);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Knowing that we check size equality before, do we need to iterate on each Map ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

@evanlucas
Copy link
Contributor Author

Comments have been addressed and additional tests have been added to verify that order does not matter.

@evanlucas
Copy link
Contributor Author

@targos
Copy link
Member

targos commented Aug 7, 2015

LGTM

@evanlucas
Copy link
Contributor Author

In addition to master, should this also be landed in 3.x?

@thefourtheye
Copy link
Contributor

function compareSet(a, b, strict) { strict is unused

@evanlucas
Copy link
Contributor Author

@thefourtheye good catch. Removed

@thefourtheye
Copy link
Contributor

// throws because we can't really check without being strict I think this comment can be done better. For sets, the importance of strict is only with the prototype comparison. It shouldnt matter for the values comparison. Probably I am missing something.

PS. I am in a forest with a bad internet connection. Sorry for leaving comments with a lot of delay :'(

@thefourtheye
Copy link
Contributor

Can we use const wherever the values are assigned only once?

@evanlucas
Copy link
Contributor Author

If we are comparing strictly, it is equivalent to using a === b. With sets, all we can do is see if set.has(a). We can't apply type coercion in that to my knowledge

@evanlucas
Copy link
Contributor Author

Updated to use const

@thefourtheye
Copy link
Contributor

Exactly. It doesn't matter if we are strict or not in this case, right?

@evanlucas
Copy link
Contributor Author

Yes, it will be "strict" in the sense that it checks strict equality regardless

@evanlucas
Copy link
Contributor Author

Unless, we were to use _deepEqual(a, b, strict) on each value.

Edit: which may be necessary.

Ex:

var set1 = new Set([ { name: 'test' } ])
var set2 = new Set([ { name: 'test' } ])
// throws
assert.deepEqual(set1, set2)

var obj = { name: 'test' }
var set1 = new Set([obj])
var set2 = new Set([obj])
// does not throw
assert.deepEqual(set1, set2)

@thefourtheye
Copy link
Contributor

Yes. Okay, if that comment is clear to others, I am okay. One minor nit, you can even ignore it, I feel that compare{Maps, Sets} would be better.

@thefourtheye
Copy link
Contributor

Nah, I don't think deepEqual is necessary as of now. What would be interesting is, when the custom comparison feature lands in the language.

Previously, assert.deepEqual used on maps and sets was not functioning
correctly.

This change adds support for correctly comparing Maps and Sets.

For Maps, comparison is done by iterating over each item in the first
Map and verifying that the key exists and the value is the same (depending
on whether strict is applied).

For Sets, each item is iterated over in the first Set and checked if it
exists in the other Set.

For both Maps and Sets, the size of both being compared is checked as
well.
@evanlucas
Copy link
Contributor Author

Now that I think about it though, should strict for sets not compare each item of sets using deepStrictEqual?

Edit: I guess that would go back to ordering being strict, so nevermind

@evanlucas
Copy link
Contributor Author

CI looks happy.

@thefourtheye
Copy link
Contributor

Then we will be contradicting the result of Set.has. IIRC, it uses strict equal comparison internally. If we use deepStrictEqual it might return true for similar objects but has will return false.

@targos
Copy link
Member

targos commented Aug 7, 2015

Is it semver-minor ?

@evanlucas
Copy link
Contributor Author

Honestly, I would think this would be considered a bug fix?

@thefourtheye
Copy link
Contributor

+1 for semver-minor

@targos
Copy link
Member

targos commented Aug 7, 2015

One could also say it is semver-major because the behaviour changes for:

var a = new Set([1, 2, 3]);
a.x = 1;
var b = {x: 1};
assert.deepEqual(a, b);

I also see it as a bug fix btw.

@thefourtheye
Copy link
Contributor

I need to check a few things, but bug fix sounds good as targos points out. We should also note that it is not a backward compatible change. Ah its confusing me. Sorry, I am not sure :-(

return false;

// iterate over all keys and check if they exist in the other
for (let kvs of a) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let is not faster than var, if this code is hotcode, we would be better to use var instead of let.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I wouldn't consider this to be hot code, but probably should stick with var for now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not faster, but performs equally in this kind of situation: http://jsperf.com/let-vs-var-iteration
I believe the optimization arrived with V8 4.4.

@domenic
Copy link
Contributor

domenic commented Aug 11, 2015

I don't think we should do this (or anything else in #2309). Assert should remain used only for testing io.js itself, and not try to be a good general assertion library. If io.js tests need this ability enough times that we need to factor it out, we should, but until then, just adding it because it'd be nice isn't a good idea IMO.

@evanlucas
Copy link
Contributor Author

@domenic fair enough. This could be done in user-land fairly easy

@evanlucas
Copy link
Contributor Author

I went ahead and published an npm package that supports this.

https://github.com/evanlucas/assert6

@sam-github
Copy link
Contributor

Now that maps and sets are in v8, its possible for node itself to start using them internally, in which case assert's deepEqual won't work for node itself. I guess we could wait until this actually happens, and some node contributor gets terribly confused, or fix it now.

josephg added a commit to josephg/node that referenced this pull request Mar 31, 2017
assert.deepEqual and assert.deepStrictEqual currently return true for
any pair of Maps and Sets regardless of content. This patch adds
support in deepEqual and deepStrictEqual to verify the contents of Maps
and Sets.

Unfortunately because there's no way to pairwise fetch set values or map
values which are equivalent but not reference-equal, this change
currently only supports reference equality checking in set values and
map key values. Equivalence checking could be done, but it would be an
O(n^2) operation, and worse, it would get slower exponentially if maps
and sets were nested.

Note that this change breaks compatibility with previous versions of
deepEqual and deepStrictEqual if consumers were depending on all maps
and sets to be seen as equivalent. The old behaviour was never
documented, but nevertheless there are certainly some tests out there
which depend on it.

Support has stalled because the assert API was frozen, but was recently
unfrozen in CTC#63

Fixes: nodejs#2309
Refs: tape-testing/tape#342
Refs: nodejs#2315
Refs: nodejs/CTC#63
addaleax pushed a commit that referenced this pull request Apr 3, 2017
assert.deepEqual and assert.deepStrictEqual currently return true for
any pair of Maps and Sets regardless of content. This patch adds
support in deepEqual and deepStrictEqual to verify the contents of Maps
and Sets.

Deeo equivalence checking is currently an
O(n^2) operation, and worse, it gets slower exponentially if maps
and sets were nested.

Note that this change breaks compatibility with previous versions of
deepEqual and deepStrictEqual if consumers were depending on all maps
and sets to be seen as equivalent. The old behaviour was never
documented, but nevertheless there are certainly some tests out there
which depend on it.

Support has stalled because the assert API was frozen, but was recently
unfrozen in CTC#63.

---

Later squashed in:

This change updates the checks for deep equality checking on Map and Set
to check all set values / all map keys to see if any of them match the
expected result.

This change is much slower, but based on the conversation in the pull
request its probably the right approach.

Fixes: #2309
Refs: tape-testing/tape#342
Refs: #2315
Refs: nodejs/CTC#63
PR-URL: #12142
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Rich Trott <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
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.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants