Files
rea/tests/boundary/process/windowsOutputCapture.test.ts
T

112 lines
3.8 KiB
TypeScript

import { createHash, randomUUID } from "node:crypto";
import { describe, expect, it } from "vitest";
import {
ProviderProcessSupervisor,
spawnOwnedProviderProcess,
} from "../../../src/process/ProviderProcess.js";
const OUTPUT_BYTES = 8 * 1024 * 1024 + 64 * 1024;
const WRITE_BYTES = 64 * 1024;
const WINDOWS_OUTPUT_DEADLINE_MS = 30_000;
const expectedDigest = (): string => {
const hash = createHash("sha256");
const chunk = Buffer.alloc(WRITE_BYTES, 0x61);
let remaining = OUTPUT_BYTES;
while (remaining > 0) {
const bytes = Math.min(remaining, chunk.length);
hash.update(chunk.subarray(0, bytes));
remaining -= bytes;
}
return hash.digest("hex");
};
describe.skipIf(process.platform !== "win32")(
"Windows owned process output capture",
() => {
it(
"drains and retains a multi-megabyte stdout stream before the workflow deadline",
async () => {
const script = [
`let remaining = ${OUTPUT_BYTES};`,
`const chunk = Buffer.alloc(${WRITE_BYTES}, 0x61);`,
"function write() {",
" while (remaining > 0) {",
" const bytes = Math.min(remaining, chunk.length);",
" remaining -= bytes;",
" if (!process.stdout.write(chunk.subarray(0, bytes))) {",
" process.stdout.once('drain', write);",
" return;",
" }",
" }",
"}",
"write();",
].join("\n");
const startedAt = performance.now();
const launched = await spawnOwnedProviderProcess({
command: process.execPath,
arguments: ["-e", script],
runId: `rea-windows-output-${randomUUID()}`,
});
const supervisor = new ProviderProcessSupervisor(
{
process: launched.process,
ownsProcessLifetime: true,
ownership: launched.ownership,
...(launched.cleanup === undefined
? {}
: { cleanup: launched.cleanup }),
},
{ maxDiagnosticBytes: OUTPUT_BYTES + WRITE_BYTES },
);
try {
const remainingDeadlineMs = Math.max(
0,
WINDOWS_OUTPUT_DEADLINE_MS - (performance.now() - startedAt),
);
expect(
await supervisor.waitForOutputClose(remainingDeadlineMs),
`expected ${OUTPUT_BYTES} bytes before the ${WINDOWS_OUTPUT_DEADLINE_MS} ms workflow deadline`,
).toBe(true);
const snapshot = supervisor.snapshot();
expect(snapshot.exitCode).toBe(0);
expect(snapshot.signal).toBeNull();
expect(snapshot.stdout.observedBytes).toBe(OUTPUT_BYTES);
expect(snapshot.stdout.bytes).toBe(OUTPUT_BYTES);
expect(snapshot.stderr.observedBytes).toBe(0);
expect(snapshot.diagnosticTruncated).toBeUndefined();
expect(snapshot.outputIncomplete).toBeUndefined();
const elapsedMs = Math.round(performance.now() - startedAt);
const digest = createHash("sha256")
.update(snapshot.stdout.text, "utf8")
.digest("hex");
expect(digest).toBe(expectedDigest());
expect(await supervisor.stop()).toMatchObject({
status: "verified-cleanup",
});
console.info(
JSON.stringify({
test: "windows-owned-process-output-capture",
bytes: snapshot.stdout.bytes,
elapsed_ms: elapsedMs,
sha256: digest,
deadline_ms: WINDOWS_OUTPUT_DEADLINE_MS,
}),
);
} finally {
const cleanup = await supervisor.stop();
supervisor.dispose();
if (cleanup.status === "incomplete")
throw new Error(
`Windows output fixture cleanup failed: ${cleanup.reason}`,
);
}
},
WINDOWS_OUTPUT_DEADLINE_MS + 5_000,
);
},
);