@@ -64,8 +64,63 @@ const GATEWAY_START_TIMEOUT_MS = 60_000;
6464const GATEWAY_STOP_TIMEOUT_MS = 1_500 ;
6565const GATEWAY_ENTRYPOINT_PREPARE_TIMEOUT_MS = 120_000 ;
6666const COMMAND_TIMEOUT_MS = 30_000 ;
67+ const LOG_TAIL_MAX_BYTES = 256 * 1024 ;
6768const entrypointPromises = new Map < string , Promise < string [ ] > > ( ) ;
6869
70+ type BoundedStringLog = string [ ] & {
71+ byteLength ?: number ;
72+ truncated ?: boolean ;
73+ } ;
74+
75+ function createBoundedStringLog ( ) : string [ ] {
76+ const log = [ ] as BoundedStringLog ;
77+ log . byteLength = 0 ;
78+ log . truncated = false ;
79+ return log ;
80+ }
81+
82+ function appendLogChunk ( log : string [ ] , chunk : unknown , maxBytes = LOG_TAIL_MAX_BYTES ) : void {
83+ const chunks = log as BoundedStringLog ;
84+ const limit = Math . max ( 1 , maxBytes ) ;
85+ const text = String ( chunk ) ;
86+ const textBytes = Buffer . byteLength ( text ) ;
87+ if ( textBytes >= limit ) {
88+ const buffer = Buffer . from ( text ) ;
89+ const tail = buffer . subarray ( buffer . length - limit ) . toString ( "utf8" ) ;
90+ chunks . splice ( 0 , chunks . length , tail ) ;
91+ chunks . byteLength = Buffer . byteLength ( tail ) ;
92+ chunks . truncated = true ;
93+ return ;
94+ }
95+
96+ chunks . push ( text ) ;
97+ chunks . byteLength = ( chunks . byteLength ?? 0 ) + textBytes ;
98+ while ( ( chunks . byteLength ?? 0 ) > limit && chunks . length > 0 ) {
99+ const first = chunks [ 0 ] ?? "" ;
100+ const firstBytes = Buffer . byteLength ( first ) ;
101+ const overflow = ( chunks . byteLength ?? 0 ) - limit ;
102+ if ( firstBytes <= overflow ) {
103+ chunks . shift ( ) ;
104+ chunks . byteLength = ( chunks . byteLength ?? 0 ) - firstBytes ;
105+ chunks . truncated = true ;
106+ continue ;
107+ }
108+
109+ const buffer = Buffer . from ( first ) ;
110+ const tail = buffer . subarray ( overflow ) . toString ( "utf8" ) ;
111+ chunks [ 0 ] = tail ;
112+ chunks . byteLength = chunks . reduce ( ( total , entry ) => total + Buffer . byteLength ( entry ) , 0 ) ;
113+ chunks . truncated = true ;
114+ }
115+ }
116+
117+ function readLogBuffer ( log : string [ ] ) : string {
118+ const text = log . join ( "" ) ;
119+ return ( log as BoundedStringLog ) . truncated
120+ ? `[output truncated to last ${ LOG_TAIL_MAX_BYTES } bytes]\n${ text } `
121+ : text ;
122+ }
123+
69124async function resolveBuiltGatewayEntrypoint ( cwd : string ) : Promise < string [ ] | null > {
70125 const buildStampPath = path . join ( cwd , "dist" , BUILD_STAMP_FILE ) ;
71126 const runtimePostBuildStampPath = path . join ( cwd , "dist" , RUNTIME_POSTBUILD_STAMP_FILE ) ;
@@ -90,17 +145,17 @@ async function prepareGatewayEntrypoint(cwd: string): Promise<string[]> {
90145 return builtEntrypoint ;
91146 }
92147
93- const stdout : string [ ] = [ ] ;
94- const stderr : string [ ] = [ ] ;
148+ const stdout = createBoundedStringLog ( ) ;
149+ const stderr = createBoundedStringLog ( ) ;
95150 const child = spawn ( "node" , [ "scripts/run-node.mjs" , "--help" ] , {
96151 cwd,
97152 env : { ...process . env , VITEST : "1" } ,
98153 stdio : [ "ignore" , "pipe" , "pipe" ] ,
99154 } ) ;
100155 child . stdout ?. setEncoding ( "utf8" ) ;
101156 child . stderr ?. setEncoding ( "utf8" ) ;
102- child . stdout ?. on ( "data" , ( d ) => stdout . push ( String ( d ) ) ) ;
103- child . stderr ?. on ( "data" , ( d ) => stderr . push ( String ( d ) ) ) ;
157+ child . stdout ?. on ( "data" , ( d ) => appendLogChunk ( stdout , d ) ) ;
158+ child . stderr ?. on ( "data" , ( d ) => appendLogChunk ( stderr , d ) ) ;
104159
105160 const completed = await Promise . race ( [
106161 new Promise < { code : number | null ; signal : NodeJS . Signals | null } > ( ( resolve , reject ) => {
@@ -112,15 +167,13 @@ async function prepareGatewayEntrypoint(cwd: string): Promise<string[]> {
112167
113168 if ( completed === null ) {
114169 child . kill ( "SIGKILL" ) ;
115- throw new Error (
116- `timeout preparing gateway entrypoint\n--- stdout ---\n${ stdout . join ( "" ) } \n--- stderr ---\n${ stderr . join ( "" ) } ` ,
117- ) ;
170+ throw new Error ( `timeout preparing gateway entrypoint\n${ formatLogs ( stdout , stderr ) } ` ) ;
118171 }
119172 if ( completed . code !== 0 ) {
120173 throw new Error (
121174 `failed preparing gateway entrypoint (code=${ String ( completed . code ) } signal=${ String (
122175 completed . signal ,
123- ) } )\n--- stdout ---\n ${ stdout . join ( "" ) } \n--- stderr ---\n ${ stderr . join ( "" ) } `,
176+ ) } )\n${ formatLogs ( stdout , stderr ) } `,
124177 ) ;
125178 }
126179
@@ -224,7 +277,7 @@ function mergeConfig(
224277}
225278
226279function formatLogs ( stdout : string [ ] , stderr : string [ ] ) : string {
227- return `--- stdout ---\n${ stdout . join ( "" ) } \n--- stderr ---\n${ stderr . join ( "" ) } ` ;
280+ return `--- stdout ---\n${ readLogBuffer ( stdout ) } \n--- stderr ---\n${ readLogBuffer ( stderr ) } ` ;
228281}
229282
230283function createInstanceEnv ( params : {
@@ -282,8 +335,8 @@ export async function createOpenClawTestInstance(
282335 ) ,
283336 ) ;
284337
285- const stdout : string [ ] = [ ] ;
286- const stderr : string [ ] = [ ] ;
338+ const stdout = createBoundedStringLog ( ) ;
339+ const stderr = createBoundedStringLog ( ) ;
287340 const env = createInstanceEnv ( {
288341 stateEnv : state . env ,
289342 extraEnv : options . env ?? { } ,
@@ -343,8 +396,8 @@ export async function createOpenClawTestInstance(
343396
344397 child . stdout ?. setEncoding ( "utf8" ) ;
345398 child . stderr ?. setEncoding ( "utf8" ) ;
346- child . stdout ?. on ( "data" , ( d ) => stdout . push ( String ( d ) ) ) ;
347- child . stderr ?. on ( "data" , ( d ) => stderr . push ( String ( d ) ) ) ;
399+ child . stdout ?. on ( "data" , ( d ) => appendLogChunk ( stdout , d ) ) ;
400+ child . stderr ?. on ( "data" , ( d ) => appendLogChunk ( stderr , d ) ) ;
348401
349402 try {
350403 await waitForPortOpen (
@@ -410,17 +463,17 @@ async function runCommand(params: {
410463 if ( ! command ) {
411464 throw new Error ( "missing command" ) ;
412465 }
413- const stdout : string [ ] = [ ] ;
414- const stderr : string [ ] = [ ] ;
466+ const stdout = createBoundedStringLog ( ) ;
467+ const stderr = createBoundedStringLog ( ) ;
415468 const child = spawn ( command , args , {
416469 cwd : params . cwd ,
417470 env : params . env ,
418471 stdio : [ "ignore" , "pipe" , "pipe" ] ,
419472 } ) ;
420473 child . stdout ?. setEncoding ( "utf8" ) ;
421474 child . stderr ?. setEncoding ( "utf8" ) ;
422- child . stdout ?. on ( "data" , ( d ) => stdout . push ( String ( d ) ) ) ;
423- child . stderr ?. on ( "data" , ( d ) => stderr . push ( String ( d ) ) ) ;
475+ child . stdout ?. on ( "data" , ( d ) => appendLogChunk ( stdout , d ) ) ;
476+ child . stderr ?. on ( "data" , ( d ) => appendLogChunk ( stderr , d ) ) ;
424477
425478 const completed = await Promise . race ( [
426479 new Promise < { code : number | null ; signal : NodeJS . Signals | null } > ( ( resolve , reject ) => {
@@ -438,7 +491,13 @@ async function runCommand(params: {
438491 }
439492 return {
440493 ...completed ,
441- stdout : stdout . join ( "" ) ,
442- stderr : stderr . join ( "" ) ,
494+ stdout : readLogBuffer ( stdout ) ,
495+ stderr : readLogBuffer ( stderr ) ,
443496 } ;
444497}
498+
499+ export const testing = {
500+ appendLogChunk,
501+ createBoundedStringLog,
502+ formatLogs,
503+ } ;
0 commit comments