Whether or not an exception should be handled when it
was thrown in the REPL's context but the REPL is already
closed is something that will depend on the details of
the specific REPL instance's use case.
Adding this option lets the REPL's creator control the
details of this behavior.
This resolves a TODO recently added in bd3cba5633.
PR-URL: https://github.com/nodejs/node/pull/62188
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
84 lines
2.8 KiB
JavaScript
84 lines
2.8 KiB
JavaScript
'use strict';
|
|
const common = require('../common');
|
|
const { start } = require('node:repl');
|
|
const assert = require('node:assert');
|
|
const { PassThrough } = require('node:stream');
|
|
const { once } = require('node:events');
|
|
const test = require('node:test');
|
|
const { spawn } = require('node:child_process');
|
|
|
|
function* generateCases() {
|
|
for (const async of [false, true]) {
|
|
for (const handleErrorReturn of ['ignore', 'print', 'unhandled', 'badvalue']) {
|
|
if (handleErrorReturn === 'badvalue' && async) {
|
|
// Handled through a separate test using a child process
|
|
continue;
|
|
}
|
|
yield { async, handleErrorReturn };
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const { async, handleErrorReturn } of generateCases()) {
|
|
test(`async: ${async}, handleErrorReturn: ${handleErrorReturn}`, async () => {
|
|
let err;
|
|
const options = {
|
|
input: new PassThrough(),
|
|
output: new PassThrough().setEncoding('utf8'),
|
|
handleError: common.mustCall((e) => {
|
|
err = e;
|
|
queueMicrotask(() => repl.emit('handled-error'));
|
|
return handleErrorReturn;
|
|
})
|
|
};
|
|
|
|
let uncaughtExceptionEvent;
|
|
if (handleErrorReturn === 'unhandled' && async) {
|
|
process.removeAllListeners('uncaughtException'); // Remove the test runner's handler
|
|
uncaughtExceptionEvent = once(process, 'uncaughtException');
|
|
}
|
|
|
|
const repl = start(options);
|
|
const inputString = async ?
|
|
'setImmediate(() => { throw new Error("testerror") })\n42\n' :
|
|
'throw new Error("testerror")\n42\n';
|
|
if (handleErrorReturn === 'badvalue') {
|
|
assert.throws(() => options.input.end(inputString), /ERR_INVALID_STATE/);
|
|
return;
|
|
}
|
|
options.input.end(inputString);
|
|
|
|
await once(repl, 'handled-error');
|
|
assert.strictEqual(err.message, 'testerror');
|
|
const outputString = options.output.read();
|
|
assert.match(outputString, /42/);
|
|
|
|
if (handleErrorReturn === 'print') {
|
|
assert.match(outputString, /testerror/);
|
|
} else {
|
|
assert.doesNotMatch(outputString, /testerror/);
|
|
}
|
|
|
|
if (uncaughtExceptionEvent) {
|
|
const [uncaughtErr] = await uncaughtExceptionEvent;
|
|
assert.strictEqual(uncaughtErr, err);
|
|
}
|
|
});
|
|
}
|
|
|
|
test('async: true, handleErrorReturn: badvalue', async () => {
|
|
// Can't test this the same way as the other combinations
|
|
// since this will take the process down in a way that
|
|
// cannot be caught.
|
|
const proc = spawn(process.execPath, ['-e', `
|
|
require('node:repl').start({
|
|
handleError: () => 'badvalue'
|
|
})
|
|
`], { encoding: 'utf8', stdio: 'pipe' });
|
|
proc.stdin.end('throw new Error("foo");');
|
|
let stderr = '';
|
|
proc.stderr.setEncoding('utf8').on('data', (data) => stderr += data);
|
|
const [exit] = await once(proc, 'close');
|
|
assert.strictEqual(exit, 1);
|
|
assert.match(stderr, /ERR_INVALID_STATE.+badvalue/);
|
|
});
|