@@ -8,6 +8,14 @@ import {
88} from '../src/plugin/pty/manager.ts'
99import type { Subprocess } from 'bun'
1010
11+ function positiveIntFromEnv ( name : string , fallback : number ) : number {
12+ const value = process . env [ name ]
13+ if ( value === undefined ) return fallback
14+
15+ const parsed = Number . parseInt ( value , 10 )
16+ return Number . isInteger ( parsed ) && parsed > 0 ? parsed : fallback
17+ }
18+
1119describe ( 'PTY Echo Behavior' , ( ) => {
1220 beforeEach ( ( ) => {
1321 initManager ( new OpencodeClient ( ) )
@@ -72,41 +80,56 @@ describe('PTY Echo Behavior', () => {
7280 }
7381
7482 it ( 'should receive initial data reproducibly' , async ( ) => {
75- const start = Date . now ( )
76- const maxRuntime = 1000
77- let runnings = 1
78- const spawned : TestSpawner [ ] = [ ]
79- while ( Date . now ( ) - start < maxRuntime ) {
80- runnings ++
81- const testSpawner = new TestSpawner ( runnings )
82- spawned . push ( testSpawner )
83- }
84- let errorMessage = ''
85- errorMessage += `[TEST] Spawned ${ runnings } subprocesses in ${ Date . now ( ) - start } ms.\n`
86- const timeout = new Promise < void > ( ( resolve ) => {
87- setTimeout ( ( ) => resolve ( ) , 20000 )
88- } )
89- const all = Promise . all ( spawned . map ( ( s ) => s . subprocess . exited ) )
90- await Promise . race ( [ all , timeout ] )
91- const stillRunning = spawned . filter ( ( s ) => s . subprocess . exitCode === null )
92- if ( stillRunning . length > 0 ) {
93- errorMessage += `[TEST] Timeout reached after 20s with ${ stillRunning . length } subprocesses still running.\n`
94- stillRunning . forEach ( ( s ) => {
95- errorMessage += `[TEST] Subprocess ${ s . testNumber } stderr: ${ s . stderrOutput } \n`
96- errorMessage += `[TEST] Subprocess ${ s . testNumber } stdout: ${ s . stdoutOutput } \n`
97- } )
98- }
99- const exitCodeNonZero = spawned . filter (
100- ( s ) => s . subprocess . exitCode !== null && s . subprocess . exitCode !== 0
83+ const repeatRuns = positiveIntFromEnv ( 'SPAWN_REPEAT_RUNS' , 64 )
84+ const repeatConcurrency = Math . min (
85+ positiveIntFromEnv ( 'SPAWN_REPEAT_CONCURRENCY' , 8 ) ,
86+ repeatRuns
10187 )
102- if ( exitCodeNonZero . length > 0 ) {
103- errorMessage += `[TEST] ${ exitCodeNonZero . length } subprocesses exited with non-zero exit code.\n`
104- exitCodeNonZero . forEach ( ( s ) => {
105- errorMessage += `[TEST] Subprocess ${ s . testNumber } stderr: ${ s . stderrOutput } \n`
106- errorMessage += `[TEST] Subprocess ${ s . testNumber } stdout: ${ s . stdoutOutput } \n`
88+ let errorMessage = ''
89+ let completedRuns = 0
90+ let failureCount = 0
91+
92+ while ( completedRuns < repeatRuns ) {
93+ const batchSize = Math . min ( repeatConcurrency , repeatRuns - completedRuns )
94+ const spawned = Array . from (
95+ { length : batchSize } ,
96+ ( _ , index ) => new TestSpawner ( completedRuns + index + 1 )
97+ )
98+
99+ const timeout = new Promise < void > ( ( resolve ) => {
100+ setTimeout ( ( ) => resolve ( ) , 20000 )
107101 } )
102+ const all = Promise . all ( spawned . map ( ( s ) => s . subprocess . exited ) )
103+ await Promise . race ( [ all , timeout ] )
104+
105+ const stillRunning = spawned . filter ( ( s ) => s . subprocess . exitCode === null )
106+ if ( stillRunning . length > 0 ) {
107+ failureCount += stillRunning . length
108+ errorMessage += `[TEST] Timeout reached after 20s with ${ stillRunning . length } subprocesses still running.\n`
109+ stillRunning . forEach ( ( s ) => {
110+ errorMessage += `[TEST] Subprocess ${ s . testNumber } stderr: ${ s . stderrOutput } \n`
111+ errorMessage += `[TEST] Subprocess ${ s . testNumber } stdout: ${ s . stdoutOutput } \n`
112+ s . subprocess . kill ( )
113+ } )
114+ }
115+
116+ const exitCodeNonZero = spawned . filter (
117+ ( s ) => s . subprocess . exitCode !== null && s . subprocess . exitCode !== 0
118+ )
119+ if ( exitCodeNonZero . length > 0 ) {
120+ failureCount += exitCodeNonZero . length
121+ errorMessage += `[TEST] ${ exitCodeNonZero . length } subprocesses exited with non-zero exit code.\n`
122+ exitCodeNonZero . forEach ( ( s ) => {
123+ errorMessage += `[TEST] Subprocess ${ s . testNumber } stderr: ${ s . stderrOutput } \n`
124+ errorMessage += `[TEST] Subprocess ${ s . testNumber } stdout: ${ s . stdoutOutput } \n`
125+ } )
126+ }
127+
128+ completedRuns += batchSize
108129 }
109- expect ( stillRunning . length + exitCodeNonZero . length , errorMessage ) . toBe ( 0 )
130+
131+ errorMessage = `[TEST] Spawned ${ completedRuns } subprocesses with concurrency ${ repeatConcurrency } .\n${ errorMessage } `
132+ expect ( failureCount , errorMessage ) . toBe ( 0 )
110133 } , 60000 )
111134
112135 it . skipIf ( ! process . env . SYNC_TESTS ) (
0 commit comments