export type GateStepExpectation = | Readonly<{ kind: "pass" }> | Readonly<{ kind: "fail"; expectedExitCode: number; expectedDiagnosticId: string; }>; export type GateProcessResult = Readonly<{ status: number | null; signal: string | null; stdout: string; stderr: string; error?: Readonly<{ code?: string }>; }>; export type GateStepClassification = | Readonly<{ kind: "EXPECTED_PASS" | "EXPECTED_FAILURE"; expectationMet: true; }> | Readonly<{ kind: "UNEXPECTED_EXIT"; expectationMet: false; }> | Readonly<{ kind: "UNEXPECTED_DIAGNOSTIC"; expectationMet: false; }> | Readonly<{ kind: "INFRASTRUCTURE_FAILURE"; expectationMet: false; detail: string; }>; /** A negative fixture passes only with its registered exit and diagnostic. */ export function classifyGateStepResult( expectation: GateStepExpectation, result: GateProcessResult, ): GateStepClassification { const errorCode = result.error?.code; const spawnFailed = result.error !== undefined; if (spawnFailed || result.signal || result.status === null) { return Object.freeze({ kind: "INFRASTRUCTURE_FAILURE" as const, expectationMet: false as const, detail: errorCode ?? result.signal ?? (spawnFailed ? "SPAWN_ERROR" : "NO_EXIT_STATUS"), }); } if (expectation.kind === "pass" && result.status === 0) { return Object.freeze({ kind: "EXPECTED_PASS" as const, expectationMet: true as const, }); } if (expectation.kind === "fail") { if (result.status !== expectation.expectedExitCode) { return Object.freeze({ kind: "UNEXPECTED_EXIT" as const, expectationMet: false as const, }); } const diagnosticOutput = `${result.stdout}\n${result.stderr}`; if (!diagnosticOutput.includes(expectation.expectedDiagnosticId)) { return Object.freeze({ kind: "UNEXPECTED_DIAGNOSTIC" as const, expectationMet: false as const, }); } return Object.freeze({ kind: "EXPECTED_FAILURE" as const, expectationMet: true as const, }); } return Object.freeze({ kind: "UNEXPECTED_EXIT" as const, expectationMet: false as const, }); }