Skip to content

Commit 2f47398

Browse files
committed
fix: use polling instead of fixed delays in background process tests
Replace setTimeout(500) with polling helpers that retry until output appears or exit code is set. Handles SSH latency variability without making tests slower than necessary.
1 parent f1c0318 commit 2f47398

File tree

1 file changed

+47
-31
lines changed

1 file changed

+47
-31
lines changed

tests/runtime/runtime.test.ts

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from "./ssh-fixture";
1818
import { createTestRuntime, TestWorkspace, type RuntimeType } from "./test-helpers";
1919
import { execBuffered, readFileString, writeFileString } from "@/node/utils/runtime/helpers";
20-
import type { Runtime } from "@/node/runtime/Runtime";
20+
import type { BackgroundHandle, Runtime } from "@/node/runtime/Runtime";
2121
import { RuntimeError } from "@/node/runtime/Runtime";
2222

2323
// Skip all tests if TEST_INTEGRATION is not set
@@ -1183,6 +1183,36 @@ describeIntegration("Runtime integration tests", () => {
11831183
// Generate unique IDs for each test to avoid conflicts
11841184
const genId = () => `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
11851185

1186+
// Polling helpers to handle SSH latency variability
1187+
async function waitForOutput(
1188+
rt: Runtime,
1189+
filePath: string,
1190+
opts?: { timeout?: number; interval?: number }
1191+
): Promise<string> {
1192+
const { timeout = 5000, interval = 100 } = opts ?? {};
1193+
const start = Date.now();
1194+
while (Date.now() - start < timeout) {
1195+
const content = await readFileString(rt, filePath);
1196+
if (content.trim()) return content;
1197+
await new Promise((r) => setTimeout(r, interval));
1198+
}
1199+
return await readFileString(rt, filePath);
1200+
}
1201+
1202+
async function waitForExitCode(
1203+
handle: BackgroundHandle,
1204+
opts?: { timeout?: number; interval?: number }
1205+
): Promise<number | null> {
1206+
const { timeout = 5000, interval = 100 } = opts ?? {};
1207+
const start = Date.now();
1208+
while (Date.now() - start < timeout) {
1209+
const code = await handle.getExitCode();
1210+
if (code !== null) return code;
1211+
await new Promise((r) => setTimeout(r, interval));
1212+
}
1213+
return await handle.getExitCode();
1214+
}
1215+
11861216
test.concurrent("spawns process and captures output to file", async () => {
11871217
const runtime = createRuntime();
11881218
await using workspace = await TestWorkspace.create(runtime, type);
@@ -1200,12 +1230,9 @@ describeIntegration("Runtime integration tests", () => {
12001230
expect(result.handle.outputDir).toContain(workspaceId);
12011231
expect(result.handle.outputDir).toMatch(/bg-[0-9a-f]{8}/);
12021232

1203-
// Wait for process to complete and output to be written
1204-
await new Promise((resolve) => setTimeout(resolve, 500));
1205-
1206-
// Read stdout from file
1233+
// Poll for output (handles SSH latency)
12071234
const stdoutPath = `${result.handle.outputDir}/stdout.log`;
1208-
const stdout = await readFileString(runtime, stdoutPath);
1235+
const stdout = await waitForOutput(runtime, stdoutPath);
12091236
expect(stdout.trim()).toBe("hello from background");
12101237

12111238
await result.handle.dispose();
@@ -1225,11 +1252,8 @@ describeIntegration("Runtime integration tests", () => {
12251252
expect(result.success).toBe(true);
12261253
if (!result.success) return;
12271254

1228-
// Wait for process to exit and trap to write exit code
1229-
await new Promise((resolve) => setTimeout(resolve, 500));
1230-
1231-
// Check exit code
1232-
const exitCode = await result.handle.getExitCode();
1255+
// Poll for exit code (handles SSH latency)
1256+
const exitCode = await waitForExitCode(result.handle);
12331257
expect(exitCode).toBe(42);
12341258

12351259
await result.handle.dispose();
@@ -1255,8 +1279,9 @@ describeIntegration("Runtime integration tests", () => {
12551279
// Terminate it
12561280
await result.handle.terminate();
12571281

1258-
// Should have exit code after termination
1259-
expect(await result.handle.getExitCode()).not.toBe(null);
1282+
// Poll for exit code after termination
1283+
const exitCode = await waitForExitCode(result.handle);
1284+
expect(exitCode).not.toBe(null);
12601285

12611286
await result.handle.dispose();
12621287
});
@@ -1281,11 +1306,9 @@ describeIntegration("Runtime integration tests", () => {
12811306
// Terminate
12821307
await result.handle.terminate();
12831308

1284-
// Give it a moment to die
1285-
await new Promise((resolve) => setTimeout(resolve, 100));
1286-
1287-
// Should have exit code (not running anymore)
1288-
expect(await result.handle.getExitCode()).not.toBe(null);
1309+
// Poll for exit code (handles SSH latency)
1310+
const exitCode = await waitForExitCode(result.handle);
1311+
expect(exitCode).not.toBe(null);
12891312

12901313
await result.handle.dispose();
12911314
});
@@ -1303,12 +1326,9 @@ describeIntegration("Runtime integration tests", () => {
13031326
expect(result.success).toBe(true);
13041327
if (!result.success) return;
13051328

1306-
// Wait for output
1307-
await new Promise((resolve) => setTimeout(resolve, 500));
1308-
1309-
// Read stderr from file
1329+
// Poll for output (handles SSH latency)
13101330
const stderrPath = `${result.handle.outputDir}/stderr.log`;
1311-
const stderr = await readFileString(runtime, stderrPath);
1331+
const stderr = await waitForOutput(runtime, stderrPath);
13121332
expect(stderr.trim()).toBe("error message");
13131333

13141334
await result.handle.dispose();
@@ -1327,11 +1347,9 @@ describeIntegration("Runtime integration tests", () => {
13271347
expect(result.success).toBe(true);
13281348
if (!result.success) return;
13291349

1330-
// Wait for output
1331-
await new Promise((resolve) => setTimeout(resolve, 500));
1332-
1350+
// Poll for output (handles SSH latency)
13331351
const stdoutPath = `${result.handle.outputDir}/stdout.log`;
1334-
const stdout = await readFileString(runtime, stdoutPath);
1352+
const stdout = await waitForOutput(runtime, stdoutPath);
13351353
expect(stdout.trim()).toBe(workspace.path);
13361354

13371355
await result.handle.dispose();
@@ -1351,11 +1369,9 @@ describeIntegration("Runtime integration tests", () => {
13511369
expect(result.success).toBe(true);
13521370
if (!result.success) return;
13531371

1354-
// Wait for output
1355-
await new Promise((resolve) => setTimeout(resolve, 500));
1356-
1372+
// Poll for output (handles SSH latency)
13571373
const stdoutPath = `${result.handle.outputDir}/stdout.log`;
1358-
const stdout = await readFileString(runtime, stdoutPath);
1374+
const stdout = await waitForOutput(runtime, stdoutPath);
13591375
expect(stdout.trim()).toBe("secret=hunter2");
13601376

13611377
await result.handle.dispose();

0 commit comments

Comments
 (0)