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

fix common.expectsError type checking #13686

Conversation

refack
Copy link
Contributor

@refack refack commented Jun 14, 2017

Fixes: #13682

  • More exact types for Errors in the tests
  • fix common.expectsError type checking

/cc @cjihrig @nodejs/testing

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

test

@nodejs-github-bot nodejs-github-bot added the test Issues and PRs related to the tests. label Jun 14, 2017
@refack refack added the wip Issues and PRs that are still a work in progress. label Jun 14, 2017
@refack
Copy link
Contributor Author

refack commented Jun 14, 2017

I'm about 50% there. Looking for general feedback.

@@ -31,7 +32,7 @@ socket.on('listening', common.mustCall(() => {
socket.bind();
}, common.expectsError({
code: 'ERR_SOCKET_ALREADY_BOUND',
type: Error,
type: e.Error,
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather this not check against the internal errors objects. These need to be recognizable as normal Error, TypeError and RangeError instances.


const expectedErr = common.expectsError({
type: Error,
Copy link
Member

Choose a reason for hiding this comment

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

Please only use common.expectsError() with errors that have been migrated to internal/errors. The reason is so that the existing places in the tests where the error message is checked can be more easily found when doing the migration. If the tests already use common.expectsError(), then it's far more likely that whoever is doing the migrating will miss those.

Date.UTC(1970, 0, 1, 0, 0, 0), // atime
Date.UTC(1970, 0, 1, 0, 0, 0), // mtime
Date.UTC(1970, 0, 1, 0, 0, 0), // ctime
Date.UTC(1970, 0, 1, 0, 0, 0) // birthtime
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated whitespace changes

assert.strictEqual(
errors.message('ERR_INVALID_URL_SCHEME', ['file']),
'The URL must be of scheme file'
);
Copy link
Member

Choose a reason for hiding this comment

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

why change the formatting on these?

@jasnell
Copy link
Member

jasnell commented Jun 14, 2017

Generally not a fan of updating the common.expectsError() checks to test against type: e.Error and such. The fact that those are internal errors is largely inconsequential. The check needs to be verifying that they are recognizable as the externally facing error types (Error, TypeError, and RangeError).

I'm definitely -1 on this PR as it currently stands.

@refack
Copy link
Contributor Author

refack commented Jun 14, 2017

Generally not a fan of updating the common.expectsError() checks to test against type: e.Error and such. The fact that those are internal errors is largely inconsequential. The check needs to be verifying that they are recognizable as the externally facing error types (Error, TypeError, and RangeError).

I'm definitely -1 on this PR as it currently stands.

Ok. Thanks for the feedback. The other solution I had in mind is to use error.constructor.name and bypass the inheritance chain.

@cjihrig
Copy link
Contributor

cjihrig commented Jun 14, 2017

The fact that those are internal errors is largely inconsequential.

I don't know if I'd say inconsequential. We've given up some things by using custom errors.

@jasnell
Copy link
Member

jasnell commented Jun 14, 2017

We've given up some things by using custom errors

Like what?

@refack refack force-pushed the common.expectError-you-would-not-expect-expectError-to-err branch 2 times, most recently from 665dca2 to f8405bc Compare June 14, 2017 23:12
@refack refack changed the title WIP: fix common.expectsError type checking fix common.expectsError type checking Jun 14, 2017
@refack refack removed the wip Issues and PRs that are still a work in progress. label Jun 14, 2017
@refack
Copy link
Contributor Author

refack commented Jun 14, 2017

Quick sanity: https://ci.nodejs.org/job/node-test-commit-linuxone/6638/
@jasnell PTAL

  1. using constructor.name I found some bugs in /parallel/test-internal-errors.js
  2. Based on fix common.expectsError type checking #13686 (comment) I added a check that expectsError is used with internal errors (a bit more lax to allow AssersionError and RangeError)
  3. found two places that used it for "regular" errors: parallel/test-fs-realpath.js & parallel/test-http-request-invalid-method-error.js

@refack
Copy link
Contributor Author

refack commented Jun 14, 2017

@refack refack self-assigned this Jun 14, 2017
@cjihrig
Copy link
Contributor

cjihrig commented Jun 15, 2017

We've given up some things by using custom errors

Like what?

Correctly identifying an error seems to be very hard to do. Even our internal isError() function partially depends on toString() behavior, which can be faked. By moving to custom errors it appears that we can't rely on the constructor (unless we identify the error as a custom internal error) or V8's IsNativeError().

@jasnell
Copy link
Member

jasnell commented Jun 15, 2017

The following works well

const errors = require('internal/errors');
const util = require('util');
const err = new err.Error('ERR_ASSERTION');

util.isError(err);     // true
err instanceof Error;  // true

err.constructor.prototype instanceof Error; // true

@cjihrig
Copy link
Contributor

cjihrig commented Jun 15, 2017

Replace:

const err = new errors.Error('ERR_ASSERTION');

With:

const err = new errors.TypeError('ERR_ASSERTION');

And all three of those still report true. That's currently broken in common.expectsError(), assuming that function should be able to distinguish Error from TypeError. Based on recent efforts in our test suite, I think thats an important feature.

@refack
Copy link
Contributor Author

refack commented Jun 16, 2017

Replace:

const err = new errors.Error('ERR_ASSERTION');
With:

const err = new errors.TypeError('ERR_ASSERTION');
And all three of those still report true. That's currently broken in common.expectsError(), assuming that function should be able to distinguish Error from TypeError. Based on recent efforts in our test suite, I think thats an important feature.

Well you two are commenting in a PR that fixes that 😉

@jasnell
Copy link
Member

jasnell commented Jun 16, 2017

And all three of those still report true.

As one should expect them to. This is not specific to common.expectsError(). Consider the following:

assert.throws(() => { throw new TypeError() }, Error);

That passes because assert.throws() uses instanceof to verify that the thrown error is Error, and since TypeError is a subclass of Error, everything is good.

The following is all correct behavior:

const err = new err.TypeError('ERR_ASSERTION');

util.isError(err);     // true
err instanceof Error;  // true

err.constructor.prototype instanceof Error; // true

@@ -225,7 +225,7 @@ function test_cyclic_link_protection(callback) {
});
assert.throws(() => {
fs.realpathSync(entry);
}, common.expectsError({ code: 'ELOOP', type: Error }));
}, /^Error: ELOOP: too many symbolic links encountered, stat/);
Copy link
Member

Choose a reason for hiding this comment

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

I'm -1 on changing this one. The check here is correct, tho the message check can be added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You asked that "regular" errors checks won't use common.expectsError.
I'll check again.

Copy link
Member

Choose a reason for hiding this comment

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

My apologies, I mean for errors that do not currently have a code property. These do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack

if (type !== undefined)
assert(error instanceof type,
`${error} is not the expected type ${type}`);
if (expectedTypeName) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not seeing why this increase in complexity is necessary. The instanceof check works just fine.

Copy link
Contributor Author

@refack refack Jun 17, 2017

Choose a reason for hiding this comment

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

Because

var e = new TypeError();
e instanceof Error === true;

Copy link
Member

Choose a reason for hiding this comment

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

but that's ok. I don't see that it needs to be stricter than that

Copy link
Contributor

Choose a reason for hiding this comment

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

For the purposes of our own test suite, I'd expect a TypeError assertion to only work with TypeErrors.

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'm with @cjihrig.

assert(err instanceof Error)

Does not protect for a change in the type of err. The code could change it from Error to TypeError and the test would pass. Even changes from TypeError to RangeError would pass.
Those are semver-major changes that we don't assert.

coreAPI.function(arg, (err, ret) => {
  if (err instanceof TypeError) console.log('bad programer!');
  if (err instanceof Error) console.log('oops');
}

Copy link
Member

Choose a reason for hiding this comment

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

if I do common.expectsError({type: TypeError}) it will only be true if the error is a TypeError (or a subclass of TypeError. If you'd like a stricter check, perhaps add a new argument like {strictType: TypeError} so that tests that might happen to require absolutely strict checking can do so. Or, perhaps even allow type: to take a function so stricter checking can be optionally performed.

Copy link
Contributor

Choose a reason for hiding this comment

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

or a subclass of TypeError

That is the problem. In a recent PR, someone used common.expectsError() with Error, when they meant TypeError. The test should have failed on this.

In our test suite we almost exclusively want the strictest error checks possible. Even if we were to add strictType (which I don't think we should), how can we achieve that without exposing the fact that we're using the internal errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the downcast case is covered, but the upcast is not (api that throw Error changed to throw TypeError, test still pass). I found one such case.
You know what will happen, we'll add strictType then we'll deprecate type and add a lint rule...
If you look at the change-set of the PR now, you'll see that enforcing strict type needs no test changes, and will protect from Errors being accidently specialized.

@refack
Copy link
Contributor Author

refack commented Jun 19, 2017

Just to sum up, there are two levels of new assertions in 4 commits:

  1. ceef6c7 - expectsError({type}) checks type name, so expectError({type: Error})(new TypeError()) will fail
  2. 2012d3f - fix 2 bugs in test-internal-errors.js
    b0c8375 - and add more assertions to that file
  3. 168fc477d1f9066f47697e7c9e02bd34e763e25e - (based on fix common.expectsError type checking #13686 (comment)) - Check that expectsError is used for node internal errors (for easy deduction of non-converted Error code paths from test code)
  4. f8405bc - Fix 2 case that violate (3)

/cc @nodejs/testing

@@ -215,10 +241,11 @@ assert.strictEqual(errors.message('ERR_MISSING_ARGS', ['name']),
assert.strictEqual(errors.message('ERR_MISSING_ARGS', ['name', 'value']),
'The "name" and "value" arguments must be specified');
assert.strictEqual(errors.message('ERR_MISSING_ARGS', ['a', 'b', 'c']),
'The "a", "b", and "c" arguments must be specified');
'The "a", "b", and "c" arguments must be specified');
Copy link
Member

Choose a reason for hiding this comment

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

Unnecessary whitespace change?

@jasnell
Copy link
Member

jasnell commented Jun 19, 2017

I'm generally still -1 on the stricter error type checking in common.expectsError() as I do not think that it is necessary.

@refack
Copy link
Contributor Author

refack commented Jun 19, 2017

@BridgeAR
Copy link
Member

BridgeAR commented Jan 6, 2018

Landed in 15016f2, 29d99c1, f26cabb

@BridgeAR BridgeAR closed this Jan 6, 2018
BridgeAR added a commit to BridgeAR/node that referenced this pull request Jan 6, 2018
PR-URL: nodejs#13686
Fixes: nodejs#13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
BridgeAR pushed a commit to BridgeAR/node that referenced this pull request Jan 6, 2018
The function should strictly test for the error class and only accept
the correct one. Any other error class should fail.

PR-URL: nodejs#13686
Fixes: nodejs#13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
BridgeAR pushed a commit to BridgeAR/node that referenced this pull request Jan 6, 2018
PR-URL: nodejs#13686
Fixes: nodejs#13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jan 8, 2018
The function should strictly test for the error class and only accept
the correct one. Any other error class should fail.

PR-URL: #13686
Fixes: #13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jan 8, 2018
PR-URL: #13686
Fixes: #13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jan 9, 2018
The function should strictly test for the error class and only accept
the correct one. Any other error class should fail.

PR-URL: #13686
Fixes: #13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
MylesBorins pushed a commit that referenced this pull request Jan 9, 2018
PR-URL: #13686
Fixes: #13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
@MylesBorins
Copy link
Contributor

This will need to be manually backported to v9.x as it is breaking a thing or two

targos pushed a commit to targos/node that referenced this pull request Mar 24, 2018
PR-URL: nodejs#13686
Fixes: nodejs#13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
targos pushed a commit to targos/node that referenced this pull request Mar 24, 2018
The function should strictly test for the error class and only accept
the correct one. Any other error class should fail.

PR-URL: nodejs#13686
Fixes: nodejs#13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
targos pushed a commit to targos/node that referenced this pull request Mar 24, 2018
PR-URL: nodejs#13686
Fixes: nodejs#13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
targos pushed a commit that referenced this pull request Mar 30, 2018
Backport-PR-URL: #19579
PR-URL: #13686
Fixes: #13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
targos pushed a commit that referenced this pull request Mar 30, 2018
The function should strictly test for the error class and only accept
the correct one. Any other error class should fail.

Backport-PR-URL: #19579
PR-URL: #13686
Fixes: #13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
targos pushed a commit that referenced this pull request Mar 30, 2018
Backport-PR-URL: #19579
PR-URL: #13686
Fixes: #13682
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
@targos targos mentioned this pull request Apr 4, 2018
@MylesBorins
Copy link
Contributor

Backport requested for 8.x in #19244

@refack refack removed their assignment Oct 12, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
test Issues and PRs related to the tests.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

test: common.expectsError() is subtly broken
10 participants