-
Notifications
You must be signed in to change notification settings - Fork 30.5k
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: improve assert rejects #19885
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The doc LGTM with some nits)
.eslintrc.js
Outdated
{ | ||
selector: `CallExpression[callee.object.name='assert'][callee.property.name='throws'][arguments.1.type='Literal']:not([arguments.1.regex])`, | ||
message: 'use a regular expression for second argument of assert.throws()', | ||
message: 'Use a object as second argument of assert.throws()', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: a object
-> an object
.
doc/api/assert.md
Outdated
function, or an object where each property will be tested for. | ||
If specified, `error` can be a [`Class`][], [`RegExp`][], a validation function, | ||
an object where each property will be tested for or an instance of error where | ||
each property will be tested for including the non-enumerable message and name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message and name
-> `message` and `name`
?
doc/api/assert.md
Outdated
Error | ||
).then(() => { | ||
// ... | ||
}); | ||
``` | ||
|
||
Note that `error` can not be a string. If a string is provided as the second |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can not
-> cannot
?
doc/api/assert.md
Outdated
function, or an object where each property will be tested for. | ||
If specified, `error` can be a [`Class`][], [`RegExp`][], a validation function, | ||
an object where each property will be tested for or an instance of error where | ||
each property will be tested for including the non-enumerable message and name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message and name
-> `message` and `name`
?
lib/.eslintrc.yaml
Outdated
- selector: "CallExpression[callee.object.name='assert'][callee.property.name='throws'][arguments.1.type='Literal']:not([arguments.1.regex])" | ||
message: "use a regular expression for second argument of assert.throws()" | ||
message: "Use a object as second argument of assert.throws()" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a object
-> an object
?
7789f2d
to
85ef34a
Compare
Rebased due to conflicts. New CI https://ci.nodejs.org/job/node-test-pull-request/14165/ |
@nodejs/testing @targos @not-an-aardvark @vsemozhetbyt @jasnell @joyeecheung @mcollina PTAL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really convinced that this is worthwhile -- it seems unnecessary to switch behavior based on the argument type when we could just use another method instead. (It also seems like the behavior isn't well-defined in edge cases where the value is both a thenable and a function simultaneously.) But I'm not strongly opposed if you think this is simpler.
lib/assert.js
Outdated
@@ -448,13 +448,24 @@ function getActual(block) { | |||
return NO_EXCEPTION_SENTINEL; | |||
} | |||
|
|||
function checkIsPromise(obj) { | |||
return isPromise(obj) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend just checking for the presence of a .then
function here, without doing any of the other checks. That would make it compatible with Promise libraries and the Promise.resolve
method and Promise libraries, as well as the mental model of "anything that can be await
-ed to get a different value".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intention was not to support Promises/A+ but ES6 promises. And if I read the spec correct, the check is fine as I implemented it. Only checking for obj !== null && typeof obj.then === 'function'
would allow multiple false positives, so I would rather stick to the current check.
I can remove the isPromise
check though as any real promise will also adhere to the later checks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you only want to support ES6 promises, why do you need the checks below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I do not only want to support native promises. Every implementation that is spec compliant should be supported out of my perspective. That is not the case for all thenables. I do not have a strong opinion on this though, so I can go for what ever is suggested. Only having the upper check is definitely not enough though. The very least that should also be checked for is typeof obj === 'object' || typeof obj === 'function
and I would argue also `typeof obj.catch === 'function'. In that case we should be relatively safe.
The difference with that check to the current implementation would be to also accept thenables that use a function instead a object as Promise
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not against the current check, but it would be nice to have a small comment explaining this in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two categories of "promise-like" objects that have special behavior in the language: native promises and thenables. By doing extra checks (e.g. for a .catch
method), we would effectively be adding a third category (the categories would be "native promises", "thenables", and "thenables which also have a .catch
method") This seems like it would complicate the model by adding an arbitrary additional check, and it's not clear to me that there is a substantial benefit to adding a .catch
check. So in my opinion, it would be slightly better to just detect thenables for API simplicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you only include the changes to assert.rejects
?
Using the big red cross because I'm -1 to make assert.throws aware of any async behavior.
85ef34a
to
c7ef1b9
Compare
@mcollina oh, I had a wrong description, it was never about |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a nit
doc/api/assert.md
Outdated
When `assert.rejects()` is called, it will immediately call the `block` | ||
function, and awaits for completion. | ||
If `block` is a function and it throws synchronously, that error will be | ||
rejected directly without checking the `error` handler. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you mean with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Synchronously thrown errors (due to the block()
call) will directly cause a rejected promise to be returned (with that error). That is different in the way that a rejection caused by calling block()
will trigger the potential error
handler (second argument). If the rejected error does not adhere to the error handler, then it will cause a rejection. Otherwise not.
I do see that this is not well worded yet and it would be great to get a suggestion for improvement. Looping in @nodejs/documentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ping @vsemozhetbyt @Trott
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot think of anything more clear(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something like this?
If
block
is a function and it throws an error synchronously,assert.rejects
will return a rejected Promise with that error without checking theerror
handler.
@not-an-aardvark my intention was not to support thenables but only ES6 Promises. I personally think it is best this way but I do not feel that strongly about it. So if others think we should support thenables as well, I am fine to accept those as well. |
@targos you reviewed #19886 and were involved in a former discussion about adding support for passing in a promise directly. So it would be nice if you could have a look :-) @TimothyGu I saw in a comment that you expected |
This adds direct promise support to `assert.rejects` and `assert.doesNotReject`. It will now accept both, functions and ES2015 promises as input. Besides this the documentation was updated to reflect the latest changes. It also refactors the tests to a non blocking way to improve the execution performance and improves the coverage.
This makes sure `assert.rejects` is always called with a second argument. Besides that it is also changes a eslint error message to suggest objects instead of a regular expression.
This adds a example to `assert.throws` to document checking against error instances. It also makes it clearer what some arguments can receive as input.
c7ef1b9
to
fa6ac5f
Compare
Rebased due to conflicts. New CI https://ci.nodejs.org/job/node-test-pull-request/14218/ |
let resultPromise; | ||
if (typeof block === 'function') { | ||
// Return a rejected promise if `block` throws synchronously. | ||
resultPromise = block(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check that block()
effectively returns a Promise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a follow up PR in #19886 that does exactly that :-)
I updated the comments according to the suggestion and added a comment next to the promise check. New CI https://ci.nodejs.org/job/node-test-pull-request/14264/ |
* `error` {RegExp|Function} | ||
* `message` {any} | ||
|
||
Awaits for the promise returned by function `block` to complete and not be | ||
rejected. | ||
Awaits the `block` promise or, if `block` is a function, immediately call the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: immediately calls the function and awaits the returned promise
Ditto on line 927
If specified, `error` can be a constructor, [`RegExp`][], a validation | ||
function, or an object where each property will be tested for. | ||
If specified, `error` can be a [`Class`][], [`RegExp`][], a validation function, | ||
an object where each property will be tested for or an instance of error where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: an object where each property will be tested for, or an instance
Ditto on line 1027
This adds direct promise support to `assert.rejects` and `assert.doesNotReject`. It will now accept both, functions and ES2015 promises as input. Besides this the documentation was updated to reflect the latest changes. It also refactors the tests to a non blocking way to improve the execution performance and improves the coverage. PR-URL: nodejs#19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
This makes sure `assert.rejects` is always called with a second argument. Besides that it is also changes a eslint error message to suggest objects instead of a regular expression. PR-URL: nodejs#19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
This adds a example to `assert.throws` to document checking against error instances. It also makes it clearer what some arguments can receive as input. PR-URL: nodejs#19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
Landed in 11819c7...1d6cdfc Nits addressed while landing. |
This adds direct promise support to `assert.rejects` and `assert.doesNotReject`. It will now accept both, functions and ES2015 promises as input. Besides this the documentation was updated to reflect the latest changes. It also refactors the tests to a non blocking way to improve the execution performance and improves the coverage. PR-URL: #19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
This makes sure `assert.rejects` is always called with a second argument. Besides that it is also changes a eslint error message to suggest objects instead of a regular expression. PR-URL: #19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
This adds a example to `assert.throws` to document checking against error instances. It also makes it clearer what some arguments can receive as input. PR-URL: #19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
This adds direct promise support to `assert.rejects` and `assert.doesNotReject`. It will now accept both, functions and ES2015 promises as input. Besides this the documentation was updated to reflect the latest changes. It also refactors the tests to a non blocking way to improve the execution performance and improves the coverage. PR-URL: nodejs#19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
This makes sure `assert.rejects` is always called with a second argument. Besides that it is also changes a eslint error message to suggest objects instead of a regular expression. PR-URL: nodejs#19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
This adds a example to `assert.throws` to document checking against error instances. It also makes it clearer what some arguments can receive as input. PR-URL: nodejs#19885 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
Commit 1 (assert: add direct promises support in rejects):
Commit 2 (tools: add eslintrc rule for
assert.rejects
):Commit 3 (doc: improve assert documentation):
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes