Skip to content

Commit 851d1b8

Browse files
committed
feat: US-110 - Add PTY echo buffer exhaustion test
1 parent 023b9b6 commit 851d1b8

1 file changed

Lines changed: 53 additions & 1 deletion

File tree

packages/kernel/test/kernel-integration.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import type { Kernel, Permissions } from "../src/types.js";
99
import { FILETYPE_PIPE, FILETYPE_CHARACTER_DEVICE } from "../src/types.js";
1010
import { filterEnv, wrapFileSystem } from "../src/permissions.js";
11-
import { MAX_CANON } from "../src/pty.js";
11+
import { MAX_CANON, MAX_PTY_BUFFER_BYTES } from "../src/pty.js";
1212

1313
describe("kernel + MockRuntimeDriver integration", () => {
1414
let kernel: Kernel;
@@ -3141,6 +3141,58 @@ describe("kernel + MockRuntimeDriver integration", () => {
31413141
proc.kill();
31423142
});
31433143

3144+
it("echo buffer exhaustion — fdWrite throws EAGAIN when output buffer is full", async () => {
3145+
const driver = new MockRuntimeDriver(["proc"], {
3146+
proc: { neverExit: true },
3147+
});
3148+
({ kernel } = await createTestKernel({ drivers: [driver] }));
3149+
3150+
const ki = driver.kernelInterface!;
3151+
const proc = kernel.spawn("proc", []);
3152+
const { masterFd, slaveFd } = ki.openpty(proc.pid);
3153+
3154+
// Default termios: canonical + echo on. Fill output buffer via slave write.
3155+
const chunk = new Uint8Array(MAX_PTY_BUFFER_BYTES);
3156+
ki.fdWrite(proc.pid, slaveFd, chunk);
3157+
3158+
// Master write with echo enabled — echo can't fit in full output buffer → EAGAIN
3159+
expect(() =>
3160+
ki.fdWrite(proc.pid, masterFd, new Uint8Array([0x41])), // 'A'
3161+
).toThrow(expect.objectContaining({ code: "EAGAIN" }));
3162+
3163+
proc.kill();
3164+
});
3165+
3166+
it("echo buffer exhaustion recovery — drain buffer, verify echo resumes", async () => {
3167+
const driver = new MockRuntimeDriver(["proc"], {
3168+
proc: { neverExit: true },
3169+
});
3170+
({ kernel } = await createTestKernel({ drivers: [driver] }));
3171+
3172+
const ki = driver.kernelInterface!;
3173+
const proc = kernel.spawn("proc", []);
3174+
const { masterFd, slaveFd } = ki.openpty(proc.pid);
3175+
3176+
// Fill output buffer via slave write
3177+
const chunk = new Uint8Array(MAX_PTY_BUFFER_BYTES);
3178+
ki.fdWrite(proc.pid, slaveFd, chunk);
3179+
3180+
// Confirm echo is blocked
3181+
expect(() =>
3182+
ki.fdWrite(proc.pid, masterFd, new Uint8Array([0x41])),
3183+
).toThrow(expect.objectContaining({ code: "EAGAIN" }));
3184+
3185+
// Drain output buffer via master read
3186+
await ki.fdRead(proc.pid, masterFd, MAX_PTY_BUFFER_BYTES);
3187+
3188+
// Echo should now work — write input, read echo back from master
3189+
ki.fdWrite(proc.pid, masterFd, new Uint8Array([0x42])); // 'B'
3190+
const echo = await ki.fdRead(proc.pid, masterFd, 1024);
3191+
expect(echo[0]).toBe(0x42); // 'B' echoed back
3192+
3193+
proc.kill();
3194+
});
3195+
31443196
it("tcgetattr on non-PTY FD throws EBADF", async () => {
31453197
const driver = new MockRuntimeDriver(["proc"], {
31463198
proc: { neverExit: true },

0 commit comments

Comments
 (0)