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

errors: make sure all Node.js errors show their properties #29677

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 62 additions & 72 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

const { Object, Math } = primordials;

const kCode = Symbol('code');
const kInfo = Symbol('info');
const messages = new Map();
const codes = {};

Expand Down Expand Up @@ -107,76 +105,81 @@ class SystemError extends Error {
writable: true,
configurable: true
});
Object.defineProperty(this, kInfo, {
configurable: false,
enumerable: false,
value: context
});
Object.defineProperty(this, kCode, {
configurable: true,
enumerable: false,
value: key,
writable: true
});
addCodeToName(this, 'SystemError', key);
}

get code() {
return this[kCode];
}
this.code = key;

set code(value) {
Object.defineProperty(this, 'code', {
configurable: true,
Object.defineProperty(this, 'info', {
value: context,
enumerable: true,
value,
writable: true
configurable: true,
writable: false
});
}

get info() {
return this[kInfo];
}

get errno() {
return this[kInfo].errno;
}

set errno(val) {
this[kInfo].errno = val;
}

get syscall() {
return this[kInfo].syscall;
}

set syscall(val) {
this[kInfo].syscall = val;
}

get path() {
return this[kInfo].path !== undefined ?
this[kInfo].path.toString() : undefined;
}
Object.defineProperty(this, 'errno', {
get() {
return context.errno;
},
set: (value) => {
context.errno = value;
},
enumerable: true,
configurable: true
});

set path(val) {
this[kInfo].path = val ?
lazyBuffer().from(val.toString()) : undefined;
}
Object.defineProperty(this, 'syscall', {
get() {
return context.syscall;
},
set: (value) => {
context.syscall = value;
},
enumerable: true,
configurable: true
});

get dest() {
return this[kInfo].path !== undefined ?
this[kInfo].dest.toString() : undefined;
}
if (context.path !== undefined) {
Object.defineProperty(this, 'path', {
get() {
return context.path != null ?
context.path.toString() : context.path;
},
set: (value) => {
context.path = value ?
lazyBuffer().from(value.toString()) : undefined;
trivikr marked this conversation as resolved.
Show resolved Hide resolved
},
enumerable: true,
configurable: true
});
}

set dest(val) {
this[kInfo].dest = val ?
lazyBuffer().from(val.toString()) : undefined;
if (context.dest !== undefined) {
Object.defineProperty(this, 'dest', {
get() {
return context.dest != null ?
context.dest.toString() : context.dest;
},
set: (value) => {
context.dest = value ?
lazyBuffer().from(value.toString()) : undefined;
},
enumerable: true,
configurable: true
});
}
}

toString() {
return `${this.name} [${this.code}]: ${this.message}`;
}

[Symbol.for('nodejs.util.inspect.custom')](recurseTimes, ctx) {
return lazyInternalUtilInspect().inspect(this, {
...ctx,
getters: true,
customInspect: false
});
}
}

function makeSystemErrorWithCode(key) {
Expand Down Expand Up @@ -207,19 +210,7 @@ function makeNodeErrorWithCode(Base, key) {
configurable: true
});
addCodeToName(this, super.name, key);
}

get code() {
return key;
}

set code(value) {
Object.defineProperty(this, 'code', {
configurable: true,
enumerable: true,
value,
writable: true
});
this.code = key;
}

toString() {
Expand Down Expand Up @@ -380,7 +371,6 @@ function uvException(ctx) {
err[prop] = ctx[prop];
}

// TODO(BridgeAR): Show the `code` property as part of the stack.
err.code = code;
if (path) {
err.path = path;
Expand Down
4 changes: 3 additions & 1 deletion test/message/internal_assert.out
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ Please open an issue with this stack trace at https://github.com/nodejs/node/iss
at *
at *
at *
at *
at * {
code: 'ERR_INTERNAL_ASSERTION'
}
4 changes: 3 additions & 1 deletion test/message/internal_assert_fail.out
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ Please open an issue with this stack trace at https://github.com/nodejs/node/iss
at *
at *
at *
at *
at * {
code: 'ERR_INTERNAL_ASSERTION'
}
23 changes: 20 additions & 3 deletions test/parallel/test-dgram-socket-buffer-size.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const common = require('../common');
const assert = require('assert');
const dgram = require('dgram');
const { inspect } = require('util');
const { SystemError } = require('internal/errors');
const { internalBinding } = require('internal/test/binding');
const {
Expand All @@ -22,7 +23,7 @@ function getExpectedError(type) {
'ENOTSOCK (socket operation on non-socket)' : 'EBADF (bad file descriptor)';
const error = {
code: 'ERR_SOCKET_BUFFER_SIZE',
type: SystemError,
name: 'SystemError',
message: `Could not get or set buffer size: ${syscall} returned ${suffix}`,
info: {
code,
Expand All @@ -40,9 +41,25 @@ function getExpectedError(type) {

const socket = dgram.createSocket('udp4');

common.expectsError(() => {
assert.throws(() => {
socket.setSendBufferSize(8192);
}, errorObj);
}, (err) => {
assert.strictEqual(
inspect(err).replace(/^ +at .*\n/gm, ''),
`SystemError [ERR_SOCKET_BUFFER_SIZE]: ${errorObj.message}\n` +
" code: 'ERR_SOCKET_BUFFER_SIZE',\n" +
' info: {\n' +
` errno: ${errorObj.info.errno},\n` +
` code: '${errorObj.info.code}',\n` +
` message: '${errorObj.info.message}',\n` +
` syscall: '${errorObj.info.syscall}'\n` +
' },\n' +
` errno: [Getter/Setter: ${errorObj.info.errno}],\n` +
` syscall: [Getter/Setter: '${errorObj.info.syscall}']\n` +
'}'
);
return true;
});

common.expectsError(() => {
socket.getSendBufferSize();
Expand Down
12 changes: 6 additions & 6 deletions test/parallel/test-internal-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ common.expectsError(() => {
{
const myError = new errors.codes.TEST_ERROR_1('foo');
assert.strictEqual(myError.code, 'TEST_ERROR_1');
assert.strictEqual(myError.hasOwnProperty('code'), false);
assert.strictEqual(myError.hasOwnProperty('code'), true);
assert.strictEqual(myError.hasOwnProperty('name'), false);
assert.deepStrictEqual(Object.keys(myError), []);
assert.deepStrictEqual(Object.keys(myError), ['code']);
const initialName = myError.name;
myError.code = 'FHQWHGADS';
assert.strictEqual(myError.code, 'FHQWHGADS');
Expand All @@ -99,11 +99,11 @@ common.expectsError(() => {
// browser. Note that `name` becomes enumerable after being assigned.
{
const myError = new errors.codes.TEST_ERROR_1('foo');
assert.deepStrictEqual(Object.keys(myError), []);
assert.deepStrictEqual(Object.keys(myError), ['code']);
const initialToString = myError.toString();

myError.name = 'Fhqwhgads';
assert.deepStrictEqual(Object.keys(myError), ['name']);
assert.deepStrictEqual(Object.keys(myError), ['code', 'name']);
assert.notStrictEqual(myError.toString(), initialToString);
}

Expand All @@ -114,7 +114,7 @@ common.expectsError(() => {
let initialConsoleLog = '';
hijackStdout((data) => { initialConsoleLog += data; });
const myError = new errors.codes.TEST_ERROR_1('foo');
assert.deepStrictEqual(Object.keys(myError), []);
assert.deepStrictEqual(Object.keys(myError), ['code']);
const initialToString = myError.toString();
console.log(myError);
assert.notStrictEqual(initialConsoleLog, '');
Expand All @@ -124,7 +124,7 @@ common.expectsError(() => {
let subsequentConsoleLog = '';
hijackStdout((data) => { subsequentConsoleLog += data; });
myError.message = 'Fhqwhgads';
assert.deepStrictEqual(Object.keys(myError), []);
assert.deepStrictEqual(Object.keys(myError), ['code']);
assert.notStrictEqual(myError.toString(), initialToString);
console.log(myError);
assert.strictEqual(subsequentConsoleLog, initialConsoleLog);
Expand Down
6 changes: 4 additions & 2 deletions test/parallel/test-repl-top-level-await.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,10 @@ async function ctrlCTest() {
]), [
'await timeout(100000)\r',
'Thrown:',
'Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' +
'Script execution was interrupted by `SIGINT`',
'[Error [ERR_SCRIPT_EXECUTION_INTERRUPTED]: ' +
'Script execution was interrupted by `SIGINT`] {',
" code: 'ERR_SCRIPT_EXECUTION_INTERRUPTED'",
'}',
Comment on lines +164 to +167
Copy link
Contributor

Choose a reason for hiding this comment

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

why did this test in particular need to change? is there something unusual about how this test works?

Copy link
Member Author

@BridgeAR BridgeAR Oct 3, 2019

Choose a reason for hiding this comment

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

The difference is that the code property became enumerable. That seems important so that users become aware that it's an actual property, not only part of .stack.

Mostly we just do not check what properties exist but only verify that specific properties contain specific values.

Copy link
Contributor

Choose a reason for hiding this comment

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

In other words, because no other tests inspect .stack this way?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not the stack that is changed here. It's the output when inspecting the error (e.g., using console.log or util.inspect). The reason is that only enumerable properties are inspected by default and others are ignored.

We rarely inspect errors in tests. Mostly we just check for the properties existence and their values. That also works with non-enumerable properties.

PROMPT
]);
}
Expand Down