mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
d98fc3859d
Derive Darwin subprocess test budgets from the bounded ownership compiler budget while preserving short command deadlines. Always join the retained command scope after test failures. Refs #5094.
160 lines
5.8 KiB
TypeScript
160 lines
5.8 KiB
TypeScript
import { chmod, readFile, stat, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { expect, it } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { closeVerifierResources } from "../../../scripts/lib/verifier-cleanup.mjs";
|
|
import { resolveSwiftcCompileTimeoutMs } from "../../../src/process/DarwinProcessRunTokenReader.js";
|
|
import {
|
|
execFileOutput,
|
|
execFileOutputFailure,
|
|
} from "../../../src/process/ExecFileOutput.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const receiptSchema = z.object({ root: z.string(), codex: z.string() });
|
|
const preparationBudgetMs =
|
|
process.platform === "darwin" ? resolveSwiftcCompileTimeoutMs() : 0;
|
|
|
|
// The executable fixture needs a POSIX shebang. Only the permission-denial
|
|
// cases additionally need an ordinary user. No auth copying or model requests run.
|
|
for (const { denied, keep } of [
|
|
{ denied: false, keep: false },
|
|
{ denied: true, keep: false },
|
|
{ denied: false, keep: true },
|
|
{ denied: true, keep: true },
|
|
]) {
|
|
it.skipIf(
|
|
process.platform === "win32" || (denied && process.getuid?.() === 0),
|
|
)(
|
|
`cleans independent evaluation files and retains failures (denied: ${String(denied)}, keep: ${String(keep)})`,
|
|
process.platform === "darwin"
|
|
? { timeout: Math.min(2_147_483_647, preparationBudgetMs + 60_000) }
|
|
: {},
|
|
async () => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-agent-eval-cleanup-",
|
|
);
|
|
const receipt = join(directory, "receipt.json");
|
|
const command = join(directory, "codex-fixture.mjs");
|
|
const parent = join(directory, "evaluations");
|
|
await writeFile(
|
|
command,
|
|
`#!${process.execPath}
|
|
import { chmodSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
if (process.argv[2] !== "--version") throw new Error("Unexpected model invocation");
|
|
const parent = ${JSON.stringify(parent)};
|
|
const entries = readdirSync(parent);
|
|
if (entries.length !== 1) throw new Error("Expected exactly one owned run");
|
|
const root = join(parent, entries[0]);
|
|
const codex = join(root, "codex");
|
|
for (const child of ["codex", "account", "targets"]) mkdirSync(join(root, child));
|
|
writeFileSync(join(codex, "auth.json"), "inert synthetic fixture");
|
|
writeFileSync(${JSON.stringify(receipt)}, JSON.stringify({ root, codex }));
|
|
if (${String(denied)}) chmodSync(codex, 0o500);
|
|
process.stderr.write("synthetic version failure");
|
|
process.exitCode = 1;
|
|
`,
|
|
{ mode: 0o700 },
|
|
);
|
|
let run: { root: string; codex: string } | undefined;
|
|
let primaryError: unknown;
|
|
try {
|
|
let stderr: string;
|
|
try {
|
|
await execFileOutput(
|
|
process.execPath,
|
|
[
|
|
fileURLToPath(
|
|
new URL(
|
|
"../../../scripts/verify-agent-experience.mjs",
|
|
import.meta.url,
|
|
),
|
|
),
|
|
],
|
|
{
|
|
env: {
|
|
...process.env,
|
|
REA_CODEX_CLI: command,
|
|
REA_AGENT_EVAL_ROOT: parent,
|
|
REA_AGENT_EVAL_TIMEOUT_MS: "10000",
|
|
REA_AGENT_EVAL_KEEP_FIXTURES: String(keep),
|
|
},
|
|
timeout: Math.min(2_147_483_647, preparationBudgetMs + 15_000),
|
|
stopSignal: "SIGTERM",
|
|
maxBuffer: 1024 * 1024,
|
|
},
|
|
);
|
|
throw new Error(
|
|
"The synthetic version failure must reject verification",
|
|
);
|
|
} catch (cause) {
|
|
const failure = execFileOutputFailure(cause);
|
|
if (failure === undefined) throw cause;
|
|
expect(failure.code).toBe(1);
|
|
stderr = failure.stderr;
|
|
}
|
|
// Cleanup must preserve the original version diagnostic too.
|
|
expect(stderr).toContain(
|
|
"--version failed with 1: synthetic version failure",
|
|
);
|
|
run = receiptSchema.parse(JSON.parse(await readFile(receipt, "utf8")));
|
|
if (denied) {
|
|
expect.soft(stderr).toContain("Verifier cleanup failed");
|
|
expect(stderr).toContain("EACCES");
|
|
expect(stderr).toContain(join(run.codex, "auth.json"));
|
|
expect(await readFile(join(run.codex, "auth.json"), "utf8")).toBe(
|
|
"inert synthetic fixture",
|
|
);
|
|
} else {
|
|
expect(stderr).not.toContain("Verifier cleanup failed");
|
|
await expect(
|
|
stat(join(run.codex, "auth.json")),
|
|
).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
}
|
|
for (const child of ["account", "targets"]) {
|
|
const entry = stat(join(run.root, child));
|
|
if (keep) expect((await entry).isDirectory()).toBe(true);
|
|
else await expect(entry).rejects.toMatchObject({ code: "ENOENT" });
|
|
}
|
|
if (!denied && !keep)
|
|
await expect(stat(run.root)).rejects.toMatchObject({
|
|
code: "ENOENT",
|
|
});
|
|
} catch (cause) {
|
|
primaryError = cause;
|
|
throw cause;
|
|
} finally {
|
|
// Restore this test's deliberately denied subtree before fixture teardown.
|
|
if (denied)
|
|
await closeVerifierResources(
|
|
[
|
|
async () => {
|
|
try {
|
|
run ??= receiptSchema.parse(
|
|
JSON.parse(await readFile(receipt, "utf8")),
|
|
);
|
|
} catch (cause) {
|
|
// Startup can fail before the executable creates its receipt.
|
|
if (
|
|
cause instanceof Error &&
|
|
"code" in cause &&
|
|
cause.code === "ENOENT"
|
|
)
|
|
return;
|
|
throw cause;
|
|
}
|
|
await chmod(run.codex, 0o700);
|
|
},
|
|
],
|
|
primaryError,
|
|
);
|
|
}
|
|
},
|
|
);
|
|
}
|