From 884779481bdf01442de3664fa4429bcc025a4db1 Mon Sep 17 00:00:00 2001 From: MBanucu Date: Wed, 28 Jan 2026 16:17:51 +0100 Subject: [PATCH 1/2] Update terminal integration tests and core logic --- src/terminal.integration.test.ts | 48 ++++++++++++++++++++++++++++++++ src/terminal.ts | 3 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/terminal.integration.test.ts b/src/terminal.integration.test.ts index f2ad04f..cec7973 100644 --- a/src/terminal.integration.test.ts +++ b/src/terminal.integration.test.ts @@ -483,4 +483,52 @@ describe.skipIf(!runIntegrationTests)("Integration Tests", () => { expect(dataReceived).toContain("HelloFromEnv"); }); + + test("Terminal onData listener receives data when set immediately after construction", async () => { + const start = Date.now(); + const maxRuntime = 4000; + let runnings = 1; + while (Date.now() - start < maxRuntime) { + console.log(`[TEST] Iteration ${runnings++}`); + const { success, stdout, stderr } = Bun.spawnSync({ + cmd: ["bun", "test", "terminal.integration.test.ts", "--test-name-pattern", "Terminal sync tests"], + stdout: "pipe", + stderr: "pipe", + env: { ...process.env, SYNC_TESTS: "1" }, + }); + expect(success, `stderr: ${stderr}`).toBe(true); + } + }); + + test.skipIf(!process.env.SYNC_TESTS)("Terminal sync tests", async () => { + let dataReceived = ""; + let hasExited = false; + + const { cmd, args } = echoCommand("sync test"); + console.log("[TEST] Starting terminal with command:", cmd, args); + const terminal = new Terminal(cmd, args); + + const exitPromise = new Promise((resolve) => { + terminal.onExit(() => { + console.log("[TEST] Process exited"); + hasExited = true; + resolve(); + }); + }); + const dataPromise = new Promise((resolve) => { + terminal.onData((data) => { + console.log("[TEST] Received data:", data); + dataReceived += data; + if (dataReceived.includes("sync test")) + resolve(); + }); + const timeout = isWindows ? 5000 : 2000; + setTimeout(() => { resolve(); }, timeout); // Timeout to avoid hanging test + }); + + await Promise.race([exitPromise, dataPromise]); + terminal.kill(); + + expect(dataReceived).toContain("sync test"); + }); }); diff --git a/src/terminal.ts b/src/terminal.ts index ec248d4..6c1efec 100644 --- a/src/terminal.ts +++ b/src/terminal.ts @@ -172,7 +172,8 @@ export class Terminal implements IPty { if (this.handle < 0) throw new Error("PTY spawn failed"); this._pid = lib.symbols.bun_pty_get_pid(this.handle); - this._startReadLoop(); + // allow constructor to finish and caller to set up event listeners + queueMicrotask(() => this._startReadLoop()); } /* ------------- accessors ------------- */ From 439855f3a131c822c9f87f56013354d0056d6320 Mon Sep 17 00:00:00 2001 From: MBanucu Date: Wed, 28 Jan 2026 16:25:43 +0100 Subject: [PATCH 2/2] test(terminal): improve cleanup in sync tests by removing manual kill calls Instead of calling terminal.kill() directly in the 'Terminal sync tests', push the terminal to the terminals array for automatic cleanup in afterEach. This ensures consistent cleanup behavior across all integration tests. --- src/terminal.integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terminal.integration.test.ts b/src/terminal.integration.test.ts index cec7973..911a720 100644 --- a/src/terminal.integration.test.ts +++ b/src/terminal.integration.test.ts @@ -507,6 +507,7 @@ describe.skipIf(!runIntegrationTests)("Integration Tests", () => { const { cmd, args } = echoCommand("sync test"); console.log("[TEST] Starting terminal with command:", cmd, args); const terminal = new Terminal(cmd, args); + terminals.push(terminal); const exitPromise = new Promise((resolve) => { terminal.onExit(() => { @@ -527,7 +528,6 @@ describe.skipIf(!runIntegrationTests)("Integration Tests", () => { }); await Promise.race([exitPromise, dataPromise]); - terminal.kill(); expect(dataReceived).toContain("sync test"); });