mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 08:17:02 +00:00
fix(native): retain completed LLDB facts across output-close cancellation
This commit is contained in:
1 parent
f3df653d9b
commit
ef64631f07
2 files changed
+108
-1
No files matched your search
@@ -1453,7 +1453,9 @@ const runLldb = async (
|
||||
const snapshot = supervisor.snapshot();
|
||||
const interruption = deadline.interruption;
|
||||
return await finish(
|
||||
interruption === undefined
|
||||
// Closed output with an observed exit owns a physical result. Read it
|
||||
// before the outer success-only cancellation projection retains facts.
|
||||
interruption === undefined || supervisor.observedExit() !== undefined
|
||||
? {
|
||||
kind: "exited",
|
||||
exitCode: snapshot.exitCode ?? null,
|
||||
|
||||
@@ -606,6 +606,111 @@ describe("LLDB process exit while output is still open", () => {
|
||||
});
|
||||
|
||||
describe("LLDB process exit at output-close loop completion", () => {
|
||||
for (const abort of [false, true])
|
||||
it.skipIf(process.platform === "win32")(
|
||||
`retains completed trace facts after output closure (cancelled=${String(abort)})`,
|
||||
async () => {
|
||||
const fixtureRoot = await mkdtemp(
|
||||
join(tmpdir(), "rea-lldb-complete-output-"),
|
||||
);
|
||||
const fakeLldb = join(fixtureRoot, "fake-lldb");
|
||||
const completed = {
|
||||
...successfulTracerOutput,
|
||||
target_output: {
|
||||
...successfulTracerOutput.target_output,
|
||||
stdout_bytes: 2,
|
||||
},
|
||||
};
|
||||
await writeFile(
|
||||
fakeLldb,
|
||||
`#!${process.execPath}
|
||||
const fs = require("node:fs");
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const command = process.argv.at(-1);
|
||||
if (!command?.startsWith("rea_trace ")) process.exit(91);
|
||||
const config = JSON.parse(fs.readFileSync(command.slice("rea_trace ".length), "utf8"));
|
||||
const child = spawnSync(process.execPath, ["-e", ""]);
|
||||
const result = ${JSON.stringify(completed)};
|
||||
result.pid = child.pid;
|
||||
fs.writeFileSync(config.pid_path, String(child.pid));
|
||||
fs.writeFileSync(config.stdout_capture_path, "ok");
|
||||
fs.writeFileSync(config.stderr_capture_path, "");
|
||||
fs.writeFileSync(config.observation_path, JSON.stringify({ kind: "event", event: result.events[0] }) + "\\n");
|
||||
fs.writeFileSync(config.result_path, JSON.stringify(result));
|
||||
process.exit(0);
|
||||
`,
|
||||
{ mode: 0o700 },
|
||||
);
|
||||
const controller = new AbortController();
|
||||
const reason = { requested: "stop after completed output closure" };
|
||||
let interrupted = false;
|
||||
const originalWait =
|
||||
ProviderProcessSupervisor.prototype.waitForOutputClose;
|
||||
const waitSpy = vi
|
||||
.spyOn(ProviderProcessSupervisor.prototype, "waitForOutputClose")
|
||||
.mockImplementation(async function (
|
||||
this: ProviderProcessSupervisor,
|
||||
timeoutMs?: number,
|
||||
signal?: AbortSignal,
|
||||
): Promise<boolean> {
|
||||
const closed = await originalWait.call(this, timeoutMs, signal);
|
||||
if (
|
||||
abort &&
|
||||
!interrupted &&
|
||||
closed &&
|
||||
this.observedExit()?.code === 0
|
||||
) {
|
||||
interrupted = true;
|
||||
controller.abort(reason);
|
||||
}
|
||||
return closed;
|
||||
});
|
||||
const tracer = new LldbCallTracer({}, undefined, async () =>
|
||||
ok({ path: fakeLldb, sha256: "e".repeat(64) }),
|
||||
);
|
||||
try {
|
||||
const result = await tracer.trace(request, controller.signal);
|
||||
expect(interrupted).toBe(abort);
|
||||
if (result.ok) {
|
||||
expect(abort).toBe(false);
|
||||
expect(result.value.run).toMatchObject(completed);
|
||||
expect(result.value.stdout).toMatchObject({
|
||||
text: "ok",
|
||||
bytes: 2,
|
||||
complete: true,
|
||||
});
|
||||
} else {
|
||||
expect(abort).toBe(true);
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
expect(
|
||||
nativeCallPartialObservationSchema.parse(
|
||||
result.error.partialObservation,
|
||||
),
|
||||
).toMatchObject({
|
||||
coverage: { status: "partial", reason: "cancelled" },
|
||||
debugger: { version: completed.version },
|
||||
process: {
|
||||
outcome: "exited",
|
||||
exit_status: 0,
|
||||
terminated: true,
|
||||
elapsed_ms: 1,
|
||||
stdout: { text: "ok", bytes: 2, complete: true },
|
||||
},
|
||||
target: { launch_identity: completed.target_identity },
|
||||
breakpoints: [{ index: 0, location_count: 1 }],
|
||||
events: [EVENT],
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
waitSpy.mockRestore();
|
||||
expect((await tracer.close()).ok).toBe(true);
|
||||
await rm(fixtureRoot, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"keeps a nonzero exit when output closure and cancellation meet at loop exit",
|
||||
async () => {
|
||||
|
||||
Reference in new issue
Block a user