mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
04221e5f01
Join admitted helper work before runtime removal, retain one preparation outcome and root, and bind originating and retry errors to exact runtime release leases with live resources. Refs #4227
161 lines
5.8 KiB
TypeScript
161 lines
5.8 KiB
TypeScript
import { access } from "node:fs/promises";
|
|
import * as filesystem from "node:fs/promises";
|
|
import { expect, it, vi, onTestFinished } from "vitest";
|
|
import * as commands from "../../../../src/process/ExecFileOutput.js";
|
|
import { createNativeUiHelperRuntime } from "../../../../src/native/NativeUiHelperRuntime.js";
|
|
import { NativeUiCleanupError } from "../../../../src/native/NativeUiCleanupError.js";
|
|
|
|
vi.mock("node:fs/promises", async (importOriginal) => {
|
|
const original = await importOriginal<typeof import("node:fs/promises")>();
|
|
return { ...original, rm: vi.fn(original.rm) };
|
|
});
|
|
|
|
it("retains the actual helper root after failed removal and releases its exact lease on retry", async () => {
|
|
const command = vi.spyOn(commands, "execFileOutput").mockResolvedValue({
|
|
stdout: "{}",
|
|
stderr: "",
|
|
stdoutBytes: 2,
|
|
stderrBytes: 0,
|
|
});
|
|
const runtime = createNativeUiHelperRuntime({});
|
|
let restoreRemoval: (() => void) | undefined;
|
|
onTestFinished(async () => {
|
|
restoreRemoval?.();
|
|
await runtime.close();
|
|
command.mockRestore();
|
|
});
|
|
await runtime.invoke({});
|
|
const root = runtime.resources()[0];
|
|
if (root === undefined) throw new Error("Expected actual owned helper root");
|
|
await expect(access(root)).resolves.toBeUndefined();
|
|
const physicalFailure = new Error("controlled helper root removal failure");
|
|
const original =
|
|
await vi.importActual<typeof import("node:fs/promises")>(
|
|
"node:fs/promises",
|
|
);
|
|
const remove = vi
|
|
.mocked(filesystem.rm)
|
|
.mockRejectedValueOnce(physicalFailure);
|
|
restoreRemoval = () => {
|
|
remove.mockReset();
|
|
remove.mockImplementation(original.rm);
|
|
};
|
|
await expect(runtime.close()).rejects.toBe(physicalFailure);
|
|
const failure = new NativeUiCleanupError(
|
|
[{ runtime, cause: physicalFailure }],
|
|
"observe_native_ui",
|
|
);
|
|
expect(runtime.cleanupLease.released).toBe(false);
|
|
expect(failure.cleanupIncomplete).toBe(true);
|
|
expect(failure.cleanupResources).toEqual([root]);
|
|
restoreRemoval();
|
|
await runtime.close();
|
|
expect(runtime.cleanupLease.released).toBe(true);
|
|
expect(failure.cleanupIncomplete).toBe(false);
|
|
expect(failure.cleanupResources).toEqual([]);
|
|
await expect(access(root)).rejects.toMatchObject({ code: "ENOENT" });
|
|
await expect(runtime.invoke({})).rejects.toThrow("scope is closed");
|
|
expect(command).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("keeps a mixed runtime aggregate incomplete until both exact owners release", async () => {
|
|
const command = vi.spyOn(commands, "execFileOutput").mockResolvedValue({
|
|
stdout: "{}",
|
|
stderr: "",
|
|
stdoutBytes: 2,
|
|
stderrBytes: 0,
|
|
});
|
|
const first = createNativeUiHelperRuntime({});
|
|
const second = createNativeUiHelperRuntime({});
|
|
onTestFinished(async () => {
|
|
await first.close();
|
|
await second.close();
|
|
command.mockRestore();
|
|
});
|
|
await first.invoke({});
|
|
await second.invoke({});
|
|
const firstRoot = first.resources()[0];
|
|
const secondRoot = second.resources()[0];
|
|
if (firstRoot === undefined || secondRoot === undefined)
|
|
throw new Error("Missing owned roots");
|
|
const failure = new NativeUiCleanupError(
|
|
[
|
|
{ runtime: first, cause: new Error("first removal failed") },
|
|
{ runtime: second, cause: new Error("second removal failed") },
|
|
],
|
|
"close_binary",
|
|
);
|
|
await first.close();
|
|
expect(failure.cleanupIncomplete).toBe(true);
|
|
expect(failure.cleanupResources).toEqual([secondRoot]);
|
|
await expect(access(firstRoot)).rejects.toMatchObject({ code: "ENOENT" });
|
|
await expect(access(secondRoot)).resolves.toBeUndefined();
|
|
await second.close();
|
|
expect(failure.cleanupIncomplete).toBe(false);
|
|
expect(failure.cleanupResources).toEqual([]);
|
|
// Repeated close remains a confirmed release.
|
|
await first.close();
|
|
expect(command).toHaveBeenCalledTimes(4);
|
|
});
|
|
|
|
it("joins admitted compilation and helper completion before removing the runtime", async () => {
|
|
let entered!: () => void, resume!: () => void;
|
|
const compiling = new Promise<void>((resolve) => {
|
|
entered = resolve;
|
|
});
|
|
const gate = new Promise<void>((resolve) => {
|
|
resume = resolve;
|
|
});
|
|
let calls = 0;
|
|
const command = vi
|
|
.spyOn(commands, "execFileOutput")
|
|
.mockImplementation(async () => {
|
|
if (++calls === 1) {
|
|
entered();
|
|
await gate;
|
|
}
|
|
return { stdout: "{}", stderr: "", stdoutBytes: 2, stderrBytes: 0 };
|
|
});
|
|
const runtime = createNativeUiHelperRuntime({});
|
|
onTestFinished(async () => {
|
|
resume();
|
|
await runtime.close();
|
|
command.mockRestore();
|
|
});
|
|
const running = runtime.invoke({});
|
|
await compiling;
|
|
const root = runtime.resources()[0];
|
|
if (root === undefined) throw new Error("Missing active root");
|
|
const closing = runtime.close();
|
|
expect(runtime.cleanupLease.released).toBe(false);
|
|
await expect(access(root)).resolves.toBeUndefined();
|
|
await expect(runtime.invoke({})).rejects.toThrow("scope is closed");
|
|
resume();
|
|
await expect(running).resolves.toEqual({});
|
|
await closing;
|
|
expect(runtime.cleanupLease.released).toBe(true);
|
|
expect(calls).toBe(2);
|
|
await expect(access(root)).rejects.toMatchObject({ code: "ENOENT" });
|
|
});
|
|
|
|
it("retains one failed compilation and its root instead of overwriting ownership on retry", async () => {
|
|
const physical = new Error("controlled compiler failure");
|
|
const command = vi
|
|
.spyOn(commands, "execFileOutput")
|
|
.mockRejectedValue(physical);
|
|
const runtime = createNativeUiHelperRuntime({});
|
|
onTestFinished(async () => {
|
|
await runtime.close();
|
|
command.mockRestore();
|
|
});
|
|
await expect(runtime.invoke({})).rejects.toBe(physical);
|
|
const roots = runtime.resources();
|
|
expect(roots).toHaveLength(1);
|
|
await expect(runtime.invoke({})).rejects.toBe(physical);
|
|
expect(runtime.resources()).toEqual(roots);
|
|
expect(command).toHaveBeenCalledTimes(1);
|
|
await runtime.close();
|
|
for (const root of roots)
|
|
await expect(access(root)).rejects.toMatchObject({ code: "ENOENT" });
|
|
});
|