diff --git a/scripts/test-group-report.mjs b/scripts/test-group-report.mjs index b86fc8231e5..8c44c0c484d 100644 --- a/scripts/test-group-report.mjs +++ b/scripts/test-group-report.mjs @@ -254,7 +254,7 @@ export function spawnText(command, args, options) { env: options.env, stdio: ["ignore", "pipe", "pipe"], }); - let output = ""; + let outputBytes = 0; let outputTail = Buffer.alloc(0); let stderrTail = Buffer.alloc(0); let streamedLogBytes = 0; @@ -383,7 +383,7 @@ export function spawnText(command, args, options) { appendTail(buffer); return; } - output += message; + appendTail(buffer); } const appendOutput = (chunk, streamName) => { if (logFd !== null) { @@ -417,10 +417,15 @@ export function spawnText(command, args, options) { return; } - output += chunk.toString("utf8"); - if (Buffer.byteLength(output) > maxBuffer) { + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), "utf8"); + outputBytes += buffer.byteLength; + appendTail(buffer); + if (streamName === "stderr") { + appendTail(buffer, "stderr"); + } + if (outputBytes > maxBuffer) { outputExceeded = true; - output += `\n[test-group-report] output exceeded ${String(maxBuffer)} bytes\n`; + appendDiagnostic(`\n[test-group-report] output exceeded ${String(maxBuffer)} bytes\n`); signalChild("SIGTERM"); scheduleKill( "[test-group-report] command did not exit after output limit; sending SIGKILL\n", @@ -444,7 +449,7 @@ export function spawnText(command, args, options) { const result = { status: outputExceeded || timedOut ? 1 : (code ?? 1), signal, - output: logFd === null ? output : streamedOutput(), + output: streamedOutput(), timedOut, }; if (waitingForKillGrace && processGroupIsAlive()) { diff --git a/test/scripts/test-group-report.test.ts b/test/scripts/test-group-report.test.ts index f37ed313e58..a5d12f3dac8 100644 --- a/test/scripts/test-group-report.test.ts +++ b/test/scripts/test-group-report.test.ts @@ -668,6 +668,32 @@ describe("scripts/test-group-report child process guard", () => { } }); + it("keeps no-log child output bounded to a tail", async () => { + const result = await spawnText( + process.execPath, + [ + "--input-type=module", + "--eval", + [ + "const chunk = Buffer.alloc(1024 * 1024, 120);", + "for (let index = 0; index < 3; index += 1) process.stdout.write(chunk);", + ].join("\n"), + ], + { + cwd: process.cwd(), + env: process.env, + killGraceMs: 50, + maxBufferBytes: 1024 * 1024, + outputTailBytes: 4096, + timeoutMs: 10_000, + }, + ); + + expect(result.status).toBe(1); + expect(result.output.length).toBeLessThan(8 * 1024); + expect(result.output).toContain("output exceeded 1048576 bytes"); + }); + it("stops streamed child output after the configured log cap", async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-test-group-report-log-cap-")); const logPath = path.join(tempDir, "child.log");