mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
d9660eb032
Join canonical owned command cleanup before releasing package verifier workspaces. Replace raw package command wrappers, the updater supervisor, WASM cleanup composite, and boolean-only retention with explicit capability ownership. Preserve selected environments, npm arguments, no-deadline calls, clean exit status, and distinct aggregate/per-stream output budgets. Exercise actual timeout descendants, cancellation, retained cleanup retry, and workspace admission.
460 lines
14 KiB
TypeScript
460 lines
14 KiB
TypeScript
import { existsSync } from "node:fs";
|
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { setTimeout as delay } from "node:timers/promises";
|
|
|
|
import { expect, it } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { createPackageVerifierCommands } from "../../../scripts/lib/verify-package-commands.mjs";
|
|
import { systemProcessOwnershipHost } from "../../../src/process/ProcessOwnershipObservation.js";
|
|
import { liveProcesses } from "../../../src/process/ProcessOwnershipProcessTree.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const observed = <Value>(promise: Promise<Value>) =>
|
|
promise.then(
|
|
(value) => ({ ok: true as const, value }),
|
|
(cause: unknown) => ({ ok: false as const, cause }),
|
|
);
|
|
|
|
const commandFixture = async (
|
|
beforeWorkspaceClose?: (path: string) => void | Promise<void>,
|
|
) => {
|
|
const parent = await createTestTempDirectory(
|
|
"rea-package-command-semantics-",
|
|
);
|
|
const path = join(parent, "workspace");
|
|
await mkdir(path);
|
|
let closeCalls = 0;
|
|
const workspace = {
|
|
path,
|
|
async close() {
|
|
closeCalls += 1;
|
|
await beforeWorkspaceClose?.(path);
|
|
await rm(path, { recursive: true, force: true });
|
|
},
|
|
};
|
|
const commands = await createPackageVerifierCommands({ workspace });
|
|
return { parent, workspace, commands, closeCalls: () => closeCalls };
|
|
};
|
|
|
|
it("admits complete stdout and stderr independently of their combined size", async () => {
|
|
const fixture = await commandFixture();
|
|
let output;
|
|
try {
|
|
output = await fixture.commands.exec(
|
|
process.execPath,
|
|
[
|
|
"-e",
|
|
'process.stdout.write("o".repeat(48)); process.stderr.write("e".repeat(48));',
|
|
],
|
|
{ maxBuffer: 64 },
|
|
);
|
|
} finally {
|
|
await fixture.commands.close();
|
|
}
|
|
|
|
expect(output).toEqual({ stdout: "o".repeat(48), stderr: "e".repeat(48) });
|
|
expect(existsSync(fixture.workspace.path)).toBe(false);
|
|
expect(fixture.closeCalls()).toBe(1);
|
|
});
|
|
|
|
it("enforces an explicit aggregate diagnostic budget across both streams", async () => {
|
|
const fixture = await commandFixture();
|
|
let outcome;
|
|
try {
|
|
outcome = await observed(
|
|
fixture.commands.exec(
|
|
process.execPath,
|
|
[
|
|
"-e",
|
|
'process.stdout.write("o".repeat(48)); process.stderr.write("e".repeat(48));',
|
|
],
|
|
{ diagnosticBytes: 64 },
|
|
),
|
|
);
|
|
} finally {
|
|
await fixture.commands.close();
|
|
}
|
|
|
|
expect(outcome).toEqual({
|
|
ok: false,
|
|
cause: expect.objectContaining({ reason: "output-limit" }),
|
|
});
|
|
expect(existsSync(fixture.workspace.path)).toBe(false);
|
|
expect(fixture.closeCalls()).toBe(1);
|
|
});
|
|
|
|
it("rejects either overflowing stream even when the command exits with status one", async () => {
|
|
const fixture = await commandFixture();
|
|
const outcomes = [];
|
|
try {
|
|
for (const stream of ["stdout", "stderr"] as const)
|
|
outcomes.push(
|
|
await observed(
|
|
fixture.commands.runWithStatus(process.execPath, [
|
|
"-e",
|
|
`process.${stream}.write("x".repeat(1024 * 1024 + 1)); process.exitCode = 1;`,
|
|
]),
|
|
),
|
|
);
|
|
} finally {
|
|
await fixture.commands.close();
|
|
}
|
|
|
|
expect(outcomes).toEqual([
|
|
{ ok: false, cause: expect.objectContaining({ reason: "output-limit" }) },
|
|
{ ok: false, cause: expect.objectContaining({ reason: "output-limit" }) },
|
|
]);
|
|
expect(existsSync(fixture.workspace.path)).toBe(false);
|
|
});
|
|
|
|
it("returns exact stdout for clean exit one and rejects exit two", async () => {
|
|
const fixture = await commandFixture();
|
|
let first;
|
|
let second;
|
|
try {
|
|
first = await fixture.commands.runWithStatus(process.execPath, [
|
|
"-e",
|
|
'process.stdout.write("selected stdout\\n "); process.stderr.write("exit one diagnostics"); process.exitCode = 1;',
|
|
]);
|
|
second = await observed(
|
|
fixture.commands.runWithStatus(process.execPath, [
|
|
"-e",
|
|
'process.stdout.write("exit two stdout"); process.stderr.write("exit two diagnostics"); process.exitCode = 2;',
|
|
]),
|
|
);
|
|
} finally {
|
|
await fixture.commands.close();
|
|
}
|
|
|
|
expect(first).toEqual({ stdout: "selected stdout\n ", status: 1 });
|
|
expect(second).toEqual({
|
|
ok: false,
|
|
cause: expect.objectContaining({
|
|
name: "PackageCommandExitError",
|
|
code: 2,
|
|
stdout: "exit two stdout",
|
|
stderr: "exit two diagnostics",
|
|
}),
|
|
});
|
|
});
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"rejects a signal exit instead of reporting status one",
|
|
async () => {
|
|
const fixture = await commandFixture();
|
|
let outcome;
|
|
try {
|
|
outcome = await observed(
|
|
fixture.commands.runWithStatus(process.execPath, [
|
|
"-e",
|
|
'process.kill(process.pid, "SIGTERM");',
|
|
]),
|
|
);
|
|
} finally {
|
|
await fixture.commands.close();
|
|
}
|
|
|
|
expect(outcome).toEqual({
|
|
ok: false,
|
|
cause: expect.objectContaining({
|
|
reason: "process",
|
|
snapshot: expect.objectContaining({ signal: "SIGTERM" }),
|
|
}),
|
|
});
|
|
},
|
|
);
|
|
|
|
it("uses the exact selected environment and npm entry without rewriting arguments", async () => {
|
|
const fixture = await commandFixture();
|
|
const entry = join(fixture.parent, "selected npm entry.mjs");
|
|
const sentinel = "REA_PACKAGE_COMMAND_AMBIENT_SENTINEL";
|
|
const previous = process.env[sentinel];
|
|
const source = `process.stdout.write(JSON.stringify({ args: process.argv.slice(2), cwd: process.cwd(), selected: process.env.REA_PACKAGE_COMMAND_SELECTED, ambient: process.env.${sentinel} ?? null }));`;
|
|
await writeFile(entry, source);
|
|
const arguments_ = [
|
|
"pack",
|
|
"value with spaces",
|
|
"--literal=$HOME",
|
|
"quote'and\"double",
|
|
];
|
|
const environment = {
|
|
...(process.platform === "win32"
|
|
? { SystemRoot: process.env.SystemRoot }
|
|
: {}),
|
|
REA_PACKAGE_COMMAND_SELECTED: "caller-selected",
|
|
npm_execpath: entry,
|
|
};
|
|
let direct;
|
|
let npm;
|
|
process.env[sentinel] = "ambient-only";
|
|
try {
|
|
direct = await fixture.commands.exec(process.execPath, ["-e", source], {
|
|
cwd: fixture.workspace.path,
|
|
env: environment,
|
|
});
|
|
npm = await fixture.commands.execNpm(arguments_, {
|
|
cwd: fixture.workspace.path,
|
|
env: environment,
|
|
});
|
|
} finally {
|
|
if (previous === undefined) delete process.env[sentinel];
|
|
else process.env[sentinel] = previous;
|
|
await fixture.commands.close();
|
|
}
|
|
|
|
expect(JSON.parse(direct!.stdout)).toEqual({
|
|
args: [],
|
|
cwd: fixture.workspace.path,
|
|
selected: "caller-selected",
|
|
ambient: null,
|
|
});
|
|
expect(JSON.parse(npm!.stdout)).toEqual({
|
|
args: arguments_,
|
|
cwd: fixture.workspace.path,
|
|
selected: "caller-selected",
|
|
ambient: null,
|
|
});
|
|
expect(direct?.stderr).toBe("");
|
|
expect(npm?.stderr).toBe("");
|
|
});
|
|
|
|
it("allows delayed commands with omitted and explicit zero timeouts", async () => {
|
|
const fixture = await commandFixture();
|
|
const outputs = [];
|
|
try {
|
|
for (const options of [{}, { timeout: 0 }])
|
|
outputs.push(
|
|
await fixture.commands.exec(
|
|
process.execPath,
|
|
["-e", 'setTimeout(() => process.stdout.write("delayed\\n"), 75);'],
|
|
options,
|
|
),
|
|
);
|
|
} finally {
|
|
await fixture.commands.close();
|
|
}
|
|
|
|
expect(outputs).toEqual([
|
|
{ stdout: "delayed\n", stderr: "" },
|
|
{ stdout: "delayed\n", stderr: "" },
|
|
]);
|
|
});
|
|
|
|
const waitForMarker = async (path: string): Promise<void> => {
|
|
const deadline = Date.now() + 5_000;
|
|
while (!existsSync(path)) {
|
|
if (Date.now() >= deadline)
|
|
throw new Error("Authored command did not become ready");
|
|
await delay(10);
|
|
}
|
|
};
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"shares concurrent close and joins the active command before removing its workspace",
|
|
async () => {
|
|
let acknowledgedPid: number | undefined;
|
|
let liveAtWorkspaceClose: readonly number[] | undefined;
|
|
let rootExistsAtCensus = false;
|
|
const fixture = await commandFixture(async (path) => {
|
|
rootExistsAtCensus = existsSync(path);
|
|
liveAtWorkspaceClose = liveProcesses(
|
|
await systemProcessOwnershipHost.listProcesses(),
|
|
)
|
|
.filter(({ pid }) => pid === acknowledgedPid)
|
|
.map(({ pid }) => pid);
|
|
});
|
|
const ready = join(fixture.parent, "ready");
|
|
const source = `
|
|
require("node:fs").writeFileSync(${JSON.stringify(ready)}, JSON.stringify({ pid: process.pid }));
|
|
setInterval(() => {}, 1000);
|
|
setTimeout(() => process.exit(0), 10000);
|
|
`;
|
|
const running = observed(
|
|
fixture.commands.exec(process.execPath, ["-e", source]),
|
|
);
|
|
let sameClose = false;
|
|
let outcome;
|
|
try {
|
|
await waitForMarker(ready);
|
|
({ pid: acknowledgedPid } = z
|
|
.object({ pid: z.number().int().positive() })
|
|
.parse(JSON.parse(await readFile(ready, "utf8"))));
|
|
const first = fixture.commands.close();
|
|
const second = fixture.commands.close();
|
|
sameClose = first === second;
|
|
await Promise.all([first, second]);
|
|
outcome = await running;
|
|
} finally {
|
|
await fixture.commands.close();
|
|
await running;
|
|
}
|
|
expect(sameClose).toBe(true);
|
|
expect(outcome).toEqual({
|
|
ok: false,
|
|
cause: expect.objectContaining({ reason: "cancelled" }),
|
|
});
|
|
expect(acknowledgedPid).toBeGreaterThan(0);
|
|
expect(liveAtWorkspaceClose).toEqual([]);
|
|
expect(rootExistsAtCensus).toBe(true);
|
|
expect(existsSync(fixture.workspace.path)).toBe(false);
|
|
expect(fixture.closeCalls()).toBe(1);
|
|
},
|
|
);
|
|
|
|
it("retains the same adopted capability and workspace after failed close, then releases each once", async () => {
|
|
const events: string[] = [];
|
|
const fixture = await commandFixture(() => {
|
|
events.push("workspace");
|
|
});
|
|
const child = join(fixture.workspace.path, "adopted");
|
|
const forbidden = join(fixture.parent, "forbidden-command");
|
|
await mkdir(child);
|
|
const owners: object[] = [];
|
|
let attempts = 0;
|
|
const capability = {
|
|
async close() {
|
|
owners.push(this);
|
|
attempts += 1;
|
|
if (attempts === 1) {
|
|
events.push("adopted-failed");
|
|
throw new Error("retry this exact adopted directory capability");
|
|
}
|
|
await rm(child, { recursive: true, force: true });
|
|
events.push("adopted-released");
|
|
},
|
|
};
|
|
fixture.commands.adopt(capability, child);
|
|
fixture.commands.adopt(capability, child);
|
|
let failure: unknown;
|
|
let retained;
|
|
let blockedBefore;
|
|
let blockedAfter;
|
|
try {
|
|
const closed = await observed(fixture.commands.close());
|
|
if (!closed.ok) failure = closed.cause;
|
|
retained = {
|
|
root: existsSync(fixture.workspace.path),
|
|
child: existsSync(child),
|
|
rootCloseCalls: fixture.closeCalls(),
|
|
};
|
|
const arguments_ = [
|
|
"-e",
|
|
`require("node:fs").writeFileSync(${JSON.stringify(forbidden)}, "unexpected launch");`,
|
|
];
|
|
blockedBefore = await observed(
|
|
fixture.commands.exec(process.execPath, arguments_, {
|
|
cwd: fixture.parent,
|
|
}),
|
|
);
|
|
await fixture.commands.close();
|
|
blockedAfter = await observed(
|
|
fixture.commands.exec(process.execPath, arguments_, {
|
|
cwd: fixture.parent,
|
|
}),
|
|
);
|
|
} finally {
|
|
await fixture.commands.close();
|
|
}
|
|
|
|
expect(failure).toMatchObject({
|
|
name: "PackageVerifierCleanupFailure",
|
|
path: fixture.workspace.path,
|
|
});
|
|
if (
|
|
!(failure instanceof Error) ||
|
|
!("cleanupOwner" in failure) ||
|
|
!("workspace" in failure)
|
|
)
|
|
throw new Error("Expected retained package cleanup authority");
|
|
expect(failure.cleanupOwner).toBe(fixture.commands);
|
|
expect(failure.workspace).toBe(fixture.workspace);
|
|
expect(retained).toEqual({ root: true, child: true, rootCloseCalls: 0 });
|
|
expect(owners).toHaveLength(2);
|
|
expect(owners[0]).toBe(capability);
|
|
expect(owners[1]).toBe(capability);
|
|
expect(events).toEqual(["adopted-failed", "adopted-released", "workspace"]);
|
|
expect(fixture.closeCalls()).toBe(1);
|
|
expect(existsSync(fixture.workspace.path)).toBe(false);
|
|
expect(existsSync(forbidden)).toBe(false);
|
|
for (const outcome of [blockedBefore, blockedAfter])
|
|
expect(outcome).toEqual({
|
|
ok: false,
|
|
cause: expect.objectContaining({
|
|
message: "Package command owner is closed",
|
|
}),
|
|
});
|
|
});
|
|
|
|
it("rejects a new cleanup owner once workspace release has started", async () => {
|
|
let markReleaseStarted!: () => void;
|
|
const releaseStarted = new Promise<void>((resolve) => {
|
|
markReleaseStarted = resolve;
|
|
});
|
|
let unblockWorkspaceClose!: () => void;
|
|
const allowRelease = new Promise<void>((resolve) => {
|
|
unblockWorkspaceClose = resolve;
|
|
});
|
|
const events: string[] = [];
|
|
const fixture = await commandFixture(async () => {
|
|
events.push("workspace-release-started");
|
|
markReleaseStarted();
|
|
await allowRelease;
|
|
events.push("workspace-close-unblocked");
|
|
});
|
|
// This resource belongs to the caller until adoption succeeds. Keep it
|
|
// outside the verifier workspace so workspace removal cannot conceal a leak.
|
|
const path = join(fixture.parent, "caller-owned-directory");
|
|
await mkdir(path);
|
|
const releases: object[] = [];
|
|
const capability = {
|
|
async close() {
|
|
releases.push(this);
|
|
await rm(path, { recursive: true, force: true });
|
|
events.push("caller-owner-released");
|
|
},
|
|
};
|
|
const closing = fixture.commands.close();
|
|
let rejection: unknown;
|
|
let retained;
|
|
try {
|
|
await releaseStarted;
|
|
try {
|
|
fixture.commands.adopt(capability, path);
|
|
} catch (cause: unknown) {
|
|
rejection = cause;
|
|
}
|
|
retained = {
|
|
workspace: existsSync(fixture.workspace.path),
|
|
callerResource: existsSync(path),
|
|
callerCloseCalls: releases.length,
|
|
};
|
|
} finally {
|
|
try {
|
|
await capability.close();
|
|
} finally {
|
|
unblockWorkspaceClose();
|
|
await closing;
|
|
await fixture.commands.close();
|
|
}
|
|
}
|
|
|
|
expect(rejection).toBeInstanceOf(Error);
|
|
expect(retained).toEqual({
|
|
workspace: true,
|
|
callerResource: true,
|
|
callerCloseCalls: 0,
|
|
});
|
|
expect(releases).toHaveLength(1);
|
|
expect(releases[0]).toBe(capability);
|
|
expect(events).toEqual([
|
|
"workspace-release-started",
|
|
"caller-owner-released",
|
|
"workspace-close-unblocked",
|
|
]);
|
|
expect(existsSync(path)).toBe(false);
|
|
expect(existsSync(fixture.workspace.path)).toBe(false);
|
|
expect(fixture.closeCalls()).toBe(1);
|
|
});
|