mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(artifacts): preserve cancellation observed during directory sync
This commit is contained in:
1 parent
c010fafe45
commit
04ba5caf63
4 files changed
+34
-8
No files matched your search
@@ -379,10 +379,12 @@ export class SafeOutputTree {
|
||||
handles.push(directory);
|
||||
await assertHandleIdentity(directory.handle, identity, path);
|
||||
await directory.handle.sync();
|
||||
abortIfNeeded(signal);
|
||||
await this.#closeHandle(directory);
|
||||
handles.pop();
|
||||
}
|
||||
await parent.handle.sync();
|
||||
abortIfNeeded(signal);
|
||||
await this.#assertExactEntries();
|
||||
abortIfNeeded(signal);
|
||||
this.#published = true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { STDIO_DEFAULT_MAX_BUFFER_SIZE } from "@modelcontextprotocol/server";
|
||||
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname } from "node:path";
|
||||
import { expect, it as test, vi } from "vitest";
|
||||
import {
|
||||
firmwareFixture,
|
||||
@@ -38,6 +39,7 @@ const gapsWithContinuations = (
|
||||
const directoryCloseFailure = vi.hoisted(() => ({
|
||||
mode: "off" as "off" | "transient" | "persistent",
|
||||
attempts: 0,
|
||||
parent: "",
|
||||
}));
|
||||
|
||||
vi.mock("node:fs/promises", async (importOriginal) => {
|
||||
@@ -48,6 +50,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
|
||||
const handle = await filesystem.open(...args);
|
||||
if (
|
||||
directoryCloseFailure.mode !== "off" &&
|
||||
String(args[0]) === directoryCloseFailure.parent &&
|
||||
(await handle.stat()).isDirectory()
|
||||
) {
|
||||
const close = handle.close.bind(handle);
|
||||
@@ -251,7 +254,7 @@ it("returns firmware output after a transient published-directory close failure"
|
||||
expect(await readFile(normalized.files[0]?.path ?? "", "utf8")).toBe(
|
||||
"firmware=true\n",
|
||||
);
|
||||
expect(closeFailure.attempts()).toBeGreaterThanOrEqual(4);
|
||||
expect(closeFailure.attempts()).toBeGreaterThanOrEqual(2);
|
||||
} finally {
|
||||
closeFailure.restore();
|
||||
}
|
||||
@@ -300,7 +303,7 @@ it("retains the complete firmware result when published output cleanup stays unc
|
||||
expect(await readFile(`${fixture.output}/rootfs/config`, "utf8")).toBe(
|
||||
"firmware=true\n",
|
||||
);
|
||||
expect(closeFailure.attempts()).toBeGreaterThanOrEqual(4);
|
||||
expect(closeFailure.attempts()).toBeGreaterThanOrEqual(2);
|
||||
} finally {
|
||||
closeFailure.restore();
|
||||
}
|
||||
@@ -631,12 +634,16 @@ const injectPublishedDirectoryCloseFailure = (persistent: boolean) => {
|
||||
.spyOn(SafeOutputTree.prototype, "commit")
|
||||
.mockImplementation(async function (this: SafeOutputTree) {
|
||||
directoryCloseFailure.mode = persistent ? "persistent" : "transient";
|
||||
// Only the parent descriptor closes after the publication cutoff. A
|
||||
// child/root close failure aborts sync before output becomes published.
|
||||
directoryCloseFailure.parent = dirname(this.outputRoot);
|
||||
await commit.call(this);
|
||||
});
|
||||
return {
|
||||
attempts: () => directoryCloseFailure.attempts,
|
||||
restore: () => {
|
||||
directoryCloseFailure.mode = "off";
|
||||
directoryCloseFailure.parent = "";
|
||||
spy.mockRestore();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,7 +35,12 @@ it.each(["existing", "missing-parent", "relative"] as const)(
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisInputError",
|
||||
issues: [{ path: ["output_directory"], reason: "invalid_value" }],
|
||||
issues: [
|
||||
{
|
||||
path: ["output_directory"],
|
||||
reason: kind === "relative" ? "invalid_format" : "invalid_value",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(launcher).not.toHaveBeenCalled();
|
||||
|
||||
@@ -443,7 +443,10 @@ it.each([null, "caller cancellation", new Error("caller cancellation")])(
|
||||
engine: "binwalk",
|
||||
operation: "inspect_firmware_regions",
|
||||
});
|
||||
expect(error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(
|
||||
error,
|
||||
error instanceof Error ? error.stack : String(error),
|
||||
).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(error.cause).toBe(reason);
|
||||
await files.close();
|
||||
},
|
||||
@@ -729,14 +732,17 @@ it("preserves primitive cancellation before publication commit and rolls back th
|
||||
const fixture = await publication(controller.signal);
|
||||
const create = SafeOutputTree.create;
|
||||
vi.spyOn(SafeOutputTree, "create").mockImplementation(async (...args) => {
|
||||
const tree = await create(...args);
|
||||
const tree = await create.call(SafeOutputTree, ...args);
|
||||
controller.abort(null);
|
||||
return tree;
|
||||
});
|
||||
const error = await publishFirmwareExtraction(fixture.context).catch(
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
expect(error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(
|
||||
error,
|
||||
error instanceof Error ? error.stack : String(error),
|
||||
).toBeInstanceOf(AnalysisCancelledError);
|
||||
if (error instanceof AnalysisCancelledError) expect(error.cause).toBeNull();
|
||||
await expect(access(fixture.context.outputDirectory)).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
@@ -765,8 +771,10 @@ it.each(["caller", "deadline"])(
|
||||
: undefined,
|
||||
);
|
||||
const create = SafeOutputTree.create;
|
||||
let outputTree: SafeOutputTree | undefined;
|
||||
vi.spyOn(SafeOutputTree, "create").mockImplementation(async (...args) => {
|
||||
const tree = await create(...args);
|
||||
const tree = await create.call(SafeOutputTree, ...args);
|
||||
outputTree = tree;
|
||||
fault.selected = tree.outputRoot;
|
||||
fault.onSync = () => controller.abort(reason);
|
||||
return tree;
|
||||
@@ -774,9 +782,13 @@ it.each(["caller", "deadline"])(
|
||||
const error = await publishFirmwareExtraction(fixture.context).catch(
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
expect(outputTree?.published).toBe(false);
|
||||
if (!(error instanceof AnalysisError))
|
||||
throw new Error("Expected typed tree failure");
|
||||
expect(error.cleanupIncomplete).toBe(true);
|
||||
expect(
|
||||
error.cleanupIncomplete,
|
||||
JSON.stringify(projectAnalysisError(error)),
|
||||
).toBe(true);
|
||||
expect(error.diagnostics?.primary_error).toMatchObject({
|
||||
code: "cleanup_incomplete",
|
||||
details: {
|
||||
|
||||
Reference in new issue
Block a user