import { access } from "node:fs/promises"; import { expect, it, vi } from "vitest"; import { AnalysisCancelledError, AnalysisResourceConstraintError, AnalysisTimeoutError, } from "../../domain/analysisErrorCore.js"; import { PrivateRuntimeRoot } from "../../process/PrivateRuntimeRoot.js"; import { ProviderProcessSupervisor, spawnOwnedProviderProcess, } from "../../process/ProviderProcess.js"; import { WEB_SOURCE_MAP_LIMITS } from "../../domain/webSourceLocation.js"; import { runSourceMapCommand, SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES, } from "./SourceMapCommand.js"; const runChild = ( signal: AbortSignal | undefined, script: string, onRuntime?: (path: string) => void, onLaunch?: () => void, ) => runSourceMapCommand( "{}", "/probe/source.map", signal === undefined ? undefined : { signal }, { prepareOwnership: async () => {}, createRuntime: async () => { const runtime = await PrivateRuntimeRoot.create({ prefix: "rea-source-map-command-test-", }); onRuntime?.(runtime.path); return runtime; }, launcher: async (options) => { const child = await spawnOwnedProviderProcess({ ...options, arguments: ["-e", script], }); onLaunch?.(); return child; }, }, ); const exitsAfterHoldingOutput = 'const {spawn}=require("node:child_process"); spawn(process.execPath,["-e","setTimeout(()=>{},400)"],{stdio:["ignore","inherit","inherit"]}); process.stderr.write("physical exit 7"); process.exit(7)'; const exitsThenDescendantExceedsStderrBudget = 'const {spawn}=require("node:child_process"); spawn(process.execPath,["-e",`setTimeout(()=>{process.stderr.write("x".repeat(1024*1024+64)); setInterval(()=>{},1000)},25)`],{stdio:["ignore","inherit","inherit"]}); process.stderr.write("physical exit 7"); process.exit(7)'; it("preserves the exact object reason for an already-aborted caller", async () => { const reason = { caller: "opaque source-map request" }; const controller = new AbortController(); controller.abort(reason); const result = await runSourceMapCommand( "{}", "/probe/source.map", { signal: controller.signal }, { prepareOwnership: async () => {}, launcher: async () => { throw new Error("must not launch"); }, }, ); expect(result.ok).toBe(false); if (result.ok) throw new Error("expected cancellation"); expect(result.error).toBeInstanceOf(AnalysisCancelledError); expect(result.error.cause).toBe(reason); }); it("preserves exact caller reason when cancellation precedes process exit", async () => { const reason = { caller: "active source-map cancellation" }; const controller = new AbortController(); let runtimePath: string | undefined; const result = await runChild( controller.signal, 'process.stdout.write("ready\\n"); setInterval(()=>{},1000)', (path) => { runtimePath = path; }, () => setTimeout(() => controller.abort(reason), 0), ); expect(result.ok).toBe(false); if (result.ok) throw new Error("expected cancellation"); expect(result.error).toBeInstanceOf(AnalysisCancelledError); expect(result.error.cause).toBe(reason); if (runtimePath === undefined) throw new Error("runtime was not acquired"); await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" }); }); it("preserves an observed nonzero exit before caller cancellation during output drain", async () => { const reason = { caller: "later than owned exit" }; const controller = new AbortController(); let runtimePath: string | undefined; let observedBeforeAbort: | { readonly code: number | null; readonly signal: NodeJS.Signals | null } | undefined; const originalWait = ProviderProcessSupervisor.prototype.waitForOutputClose; const waitSpy = vi .spyOn(ProviderProcessSupervisor.prototype, "waitForOutputClose") .mockImplementation(async function ( this: ProviderProcessSupervisor, timeoutMs, signal, ) { const exit = this.observedExit(); if (exit?.code === 7 && !controller.signal.aborted) { observedBeforeAbort = exit; controller.abort(reason); return false; } return originalWait.call(this, timeoutMs, signal); }); let result; try { result = await runChild( controller.signal, exitsAfterHoldingOutput, (path) => { runtimePath = path; }, ); } finally { waitSpy.mockRestore(); } expect(observedBeforeAbort).toEqual({ code: 7, signal: null }); expect(result.ok).toBe(false); if (result.ok) throw new Error("expected physical failure"); expect(result.error._tag).toBe("ProviderAdapterError"); expect(result.error.diagnostics).toMatchObject({ exit_code: 7, output_complete: false, }); expect(result.error.capturedOutput?.truncated).toBe(true); if (runtimePath === undefined) throw new Error("runtime was not acquired"); await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" }); }); it("classifies the same nonzero exit without cancellation", async () => { const result = await runChild(undefined, exitsAfterHoldingOutput); expect(result.ok).toBe(false); if (result.ok) throw new Error("expected physical failure"); expect(result.error).toMatchObject({ _tag: "ProviderAdapterError", diagnostics: { exit_code: 7, stderr: "physical exit 7", output_complete: true, }, }); }); it("stops a held drain after late output exceeds budget but keeps the exit primary", async () => { const startedAt = performance.now(); const result = await runChild( undefined, exitsThenDescendantExceedsStderrBudget, ); const elapsedMs = performance.now() - startedAt; expect(result.ok).toBe(false); if (result.ok) throw new Error("expected physical failure"); expect(result.error._tag).toBe("ProviderAdapterError"); expect(result.error.diagnostics).toMatchObject({ exit_code: 7, output_limit_reached: true, output_complete: false, }); expect(result.error.diagnostics?.stderr).toContain("physical exit 7"); expect(result.error.capturedOutput?.truncated).toBe(true); expect(elapsedMs).toBeLessThan(5_000); }); it("preserves an observed process-memory limit over the codec exit status", async () => { const controller = new AbortController(); const reason = { caller: "after memory-limit exit" }; let observedBeforeAbort: | { readonly code: number | null; readonly signal: NodeJS.Signals | null } | undefined; const originalWait = ProviderProcessSupervisor.prototype.waitForOutputClose; const waitSpy = vi .spyOn(ProviderProcessSupervisor.prototype, "waitForOutputClose") .mockImplementation(async function ( this: ProviderProcessSupervisor, timeoutMs, signal, ) { const exit = this.observedExit(); if (exit?.code === 3 && !controller.signal.aborted) { observedBeforeAbort = exit; controller.abort(reason); return false; } return originalWait.call(this, timeoutMs, signal); }); let result; try { result = await runSourceMapCommand( "{}", "/probe/source.map", { signal: controller.signal }, { prepareOwnership: async () => {}, launcher: async (options) => { const spawned = await spawnOwnedProviderProcess({ ...options, arguments: [ "-e", 'const {spawn}=require("node:child_process"); spawn(process.execPath,["-e","setTimeout(()=>{},400)"],{stdio:["ignore","inherit","inherit"]}); process.stderr.write("memory limit reached"); process.exit(3)', ], }); let exited = false; spawned.process.once("exit", () => { exited = true; }); Object.defineProperty(spawned.process, "memoryLimitReached", { get: () => exited, }); return spawned; }, }, ); } finally { waitSpy.mockRestore(); } expect(observedBeforeAbort).toEqual({ code: 3, signal: null }); expect(result.ok).toBe(false); if (result.ok) throw new Error("expected memory-limit failure"); expect(result.error).toBeInstanceOf(AnalysisResourceConstraintError); expect(result.error).toMatchObject({ resource: "memory", reportedLimits: { codec_process_memory_limit_bytes: SOURCE_MAP_CODEC_MEMORY_LIMIT_BYTES, }, capturedOutput: { stderr: "memory limit reached" }, }); expect(result.error.capturedOutput?.truncated).toBe(true); }); it("keeps a timeout distinct from caller cancellation", async () => { vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "performance"], }); let runtimePath: string | undefined; try { const result = await runSourceMapCommand( "{}", "/probe/source.map", undefined, { prepareOwnership: async () => {}, createRuntime: async () => { const runtime = await PrivateRuntimeRoot.create({ prefix: "rea-source-map-timeout-test-", }); runtimePath = runtime.path; return runtime; }, launcher: async () => { await vi.advanceTimersByTimeAsync( WEB_SOURCE_MAP_LIMITS.decodeTimeoutMs + 1, ); throw new Error("launcher settled after deadline"); }, }, ); expect(result.ok).toBe(false); if (result.ok) throw new Error("expected timeout"); expect(result.error).toBeInstanceOf(AnalysisTimeoutError); if (runtimePath === undefined) throw new Error("runtime was not acquired"); await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" }); } finally { vi.useRealTimers(); } }); it("applies a non-default decode deadline and releases its runtime", async () => { vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "performance"], }); let runtimePath: string | undefined; try { const result = await runSourceMapCommand( "{}", "/probe/source.map", undefined, { prepareOwnership: async () => {}, createRuntime: async () => { const runtime = await PrivateRuntimeRoot.create({ prefix: "rea-source-map-selected-deadline-", }); runtimePath = runtime.path; return runtime; }, launcher: async () => { await vi.advanceTimersByTimeAsync(251); throw new Error("launcher settled after deadline"); }, }, 250, ); expect(result.ok).toBe(false); if (result.ok) throw new Error("expected timeout"); expect(result.error).toBeInstanceOf(AnalysisTimeoutError); expect(result.error.userMessage).toContain("250"); expect(result.error.userMessage).toContain("decode_timeout_ms"); if (runtimePath === undefined) throw new Error("runtime was not acquired"); await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" }); } finally { vi.useRealTimers(); } });