mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
fix(source-maps): retain primary failures through cleanup retries
This commit is contained in:
1 parent
42c16ba631
commit
760d9f6019
6 files changed
+207
-60
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { access } from "node:fs/promises";
|
||||
import { expect, it, vi } from "vitest";
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisResourceConstraintError,
|
||||
@@ -14,8 +14,18 @@ import { WEB_SOURCE_MAP_LIMITS } from "../../domain/webSourceLocation.js";
|
||||
import {
|
||||
runSourceMapCommand,
|
||||
SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES,
|
||||
type SourceMapCleanupRetention,
|
||||
} from "./SourceMapCommand.js";
|
||||
|
||||
const unexpectedCleanup: SourceMapCleanupRetention = {};
|
||||
afterEach(async () => {
|
||||
const owner = unexpectedCleanup.owner;
|
||||
const failure = await owner?.close();
|
||||
if (failure !== undefined) throw failure;
|
||||
unexpectedCleanup.owner = undefined;
|
||||
expect(owner).toBeUndefined();
|
||||
});
|
||||
|
||||
const runChild = (
|
||||
signal: AbortSignal | undefined,
|
||||
script: string,
|
||||
@@ -44,6 +54,7 @@ const runChild = (
|
||||
return child;
|
||||
},
|
||||
},
|
||||
unexpectedCleanup,
|
||||
);
|
||||
|
||||
const exitsAfterHoldingOutput =
|
||||
@@ -65,6 +76,7 @@ it("preserves the exact object reason for an already-aborted caller", async () =
|
||||
throw new Error("must not launch");
|
||||
},
|
||||
},
|
||||
unexpectedCleanup,
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("expected cancellation");
|
||||
@@ -72,6 +84,67 @@ it("preserves the exact object reason for an already-aborted caller", async () =
|
||||
expect(result.error.cause).toBe(reason);
|
||||
});
|
||||
|
||||
it.each([null, { caller: "late source-map cancellation" }])(
|
||||
"retains a late caller reason %j when runtime cleanup first fails",
|
||||
async (reason) => {
|
||||
const root = await PrivateRuntimeRoot.create({
|
||||
prefix: "rea-source-map-late-cleanup-test-",
|
||||
});
|
||||
const controller = new AbortController();
|
||||
let cleanupAttempts = 0;
|
||||
const retainedCleanup: SourceMapCleanupRetention = {};
|
||||
try {
|
||||
const result = await runSourceMapCommand(
|
||||
"{}",
|
||||
"/probe/source.map",
|
||||
{ signal: controller.signal },
|
||||
{
|
||||
prepareOwnership: async () => {},
|
||||
createRuntime: async () => ({
|
||||
path: root.path,
|
||||
close: async () => {
|
||||
cleanupAttempts += 1;
|
||||
if (cleanupAttempts === 1) {
|
||||
controller.abort(reason);
|
||||
throw new Error("controlled late cleanup failure");
|
||||
}
|
||||
await root.close();
|
||||
},
|
||||
}),
|
||||
launcher: async (options) =>
|
||||
spawnOwnedProviderProcess({
|
||||
...options,
|
||||
arguments: ["-e", 'process.stdout.write("complete")'],
|
||||
}),
|
||||
},
|
||||
retainedCleanup,
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok)
|
||||
throw new Error("expected cancellation with cleanup failure");
|
||||
expect(result.error.cleanupPrimary).toBeInstanceOf(
|
||||
AnalysisCancelledError,
|
||||
);
|
||||
expect(result.error.cleanupPrimary?.cause).toBe(reason);
|
||||
expect(result.error.cleanupResources).toEqual([root.path]);
|
||||
expect(result.error.cleanupIncomplete).toBe(true);
|
||||
expect(retainedCleanup.owner).toBeDefined();
|
||||
await retainedCleanup.owner?.close();
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
expect(result.error.cleanupResources).toEqual([]);
|
||||
expect(result.error.cleanupPrimary?.cause).toBe(reason);
|
||||
} finally {
|
||||
await retainedCleanup.owner?.close();
|
||||
await root.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("returns completed codec output after successful cleanup", async () => {
|
||||
const result = await runChild(undefined, 'process.stdout.write("complete")');
|
||||
expect(result).toEqual({ ok: true, value: "complete" });
|
||||
});
|
||||
|
||||
it("preserves exact caller reason when cancellation precedes process exit", async () => {
|
||||
const reason = { caller: "active source-map cancellation" };
|
||||
const controller = new AbortController();
|
||||
@@ -222,6 +295,7 @@ it("preserves an observed process-memory limit over the codec exit status", asyn
|
||||
return spawned;
|
||||
},
|
||||
},
|
||||
unexpectedCleanup,
|
||||
);
|
||||
} finally {
|
||||
waitSpy.mockRestore();
|
||||
@@ -266,6 +340,7 @@ it("keeps a timeout distinct from caller cancellation", async () => {
|
||||
throw new Error("launcher settled after deadline");
|
||||
},
|
||||
},
|
||||
unexpectedCleanup,
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("expected timeout");
|
||||
@@ -301,6 +376,7 @@ it("applies a non-default decode deadline and releases its runtime", async () =>
|
||||
throw new Error("launcher settled after deadline");
|
||||
},
|
||||
},
|
||||
unexpectedCleanup,
|
||||
250,
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
|
||||
Reference in new issue
Block a user