Files
tech-log-frontend/scripts/lib/ci-step-result.ts

83 lines
2.2 KiB
TypeScript

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,
});
}