Compare commits

...

5 Commits

Author SHA1 Message Date
Don Isaac
faab0ab173 use bunExe and bunEnv 2024-12-06 20:52:29 -08:00
Don Isaac
5ed197ca34 more test cases 2024-12-06 17:51:50 -08:00
Don Isaac
0f197aff90 fix: handle missing close() on input 2024-12-06 17:51:37 -08:00
Don Isaac
f91c4bcede add test cases 2024-12-06 17:32:15 -08:00
Don Isaac
eb6cd28273 fix(node/readline): close input when readline interface closes 2024-12-06 17:31:50 -08:00
7 changed files with 76 additions and 0 deletions

View File

@@ -1583,6 +1583,7 @@ var _Interface = class Interface extends InterfaceConstructor {
this[kSetRawMode](false);
}
this.closed = true;
this.input.close?.();
this.emit("close");
}

View File

View File

@@ -0,0 +1 @@
foo

View File

@@ -0,0 +1,12 @@
import readline from "readline";
import fs from "fs";
import path from "path";
const emptyFile = fs.createReadStream(path.resolve(__dirname, "empty.txt"), "utf8");
const rl1 = readline.createInterface({
input: emptyFile,
output: process.stdout,
});
rl1.close();

View File

@@ -0,0 +1,12 @@
import readline from "readline";
import fs from "fs";
import path from "path";
const nonEmptyFile = fs.createReadStream(path.resolve(__dirname, "not-empty.txt"), "utf8");
const rl1 = readline.createInterface({
input: nonEmptyFile,
output: process.stdout,
});
rl1.close();

View File

@@ -0,0 +1,8 @@
import readline from "readline";
const rl1 = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl1.close();

View File

@@ -1,8 +1,10 @@
// @ts-nocheck
import { bunEnv, bunExe } from "harness";
import { createTest } from "node-harness";
import { EventEmitter } from "node:events";
import readline from "node:readline";
import { PassThrough, Writable } from "node:stream";
import path from "path";
const { beforeEach, describe, it, createDoneDotAll, createCallCheckCtx, assert } = createTest(import.meta.path);
var {
@@ -15,6 +17,8 @@ var {
// Helpers
// ----------------------------------------------------------------------------
/** Get an absolute path to a `readline` fixture. */
const fixture = (name: string): string => path.resolve(import.meta.dirname, "fixtures", name);
class TestWritable extends Writable {
data;
constructor() {
@@ -1327,6 +1331,44 @@ describe("readline.Interface", () => {
assert.strictEqual(getStringWidth("\u0301\u200D\u200E"), 0);
});
const testForEventLoopHangs = (fixtureName: string) => async () => {
// race a timeout and the subprocess promise
const subprocess = Bun.spawn({
cmd: [bunExe(), "run", fixture(fixtureName)],
env: bunEnv,
});
const timeoutAfter = 2_500;
var _timer: Timer | undefined;
const timeout = new Promise((_, reject) => {
_timer = setTimeout(() => {
_timer = undefined;
resolve(); // we're gonna race these
}, timeoutAfter);
});
await Promise.race([subprocess.exited, timeout]);
if (_timer) clearTimeout(_timer);
if (subprocess.exitCode == null) {
throw new Error("readline did not close after 2.5s. This means stdint is keeping the event loop alive.");
} else if (subprocess.exitCode !== 0) {
throw new Error(`subprocess exited with a non-zero code: ${subprocess.exitCode}`);
}
};
it(
"should not hang when stdin is immediately and synchronously closed",
testForEventLoopHangs("readline-stdin-immediate-close.ts"),
);
it(
"should not hang when an empty file is immediately and synchronously closed",
testForEventLoopHangs("readline-empty-file-immediate-close.ts"),
);
it(
"should not hang when a non-empty file is immediately and synchronously closed",
testForEventLoopHangs("readline-nonempty-file-immediate-close.ts"),
);
// // Check if vt control chars are stripped
// assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> ');
// assert.strictEqual(