@@ -17,7 +17,7 @@ import {
1717} from "./ssh-fixture" ;
1818import { createTestRuntime , TestWorkspace , type RuntimeType } from "./test-helpers" ;
1919import { 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" ;
2121import { 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 ( / b g - [ 0 - 9 a - 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