refactor: 리펙토링
This commit is contained in:
+104
-13
@@ -8,12 +8,21 @@ import {
|
||||
isValidCommitSha,
|
||||
isValidSourceDateEpoch,
|
||||
} from "./lib/build-environment.ts";
|
||||
import { classifyGateStepResult } from "./lib/ci-step-result.ts";
|
||||
|
||||
type GateStep = Readonly<{
|
||||
type GateStepBase = Readonly<{
|
||||
script: string;
|
||||
args?: readonly string[];
|
||||
expect: "pass" | "fail";
|
||||
timeoutMs?: number;
|
||||
}>;
|
||||
type GateStep =
|
||||
| (GateStepBase & Readonly<{ expect: "pass" }>)
|
||||
| (GateStepBase &
|
||||
Readonly<{
|
||||
expect: "fail";
|
||||
expectedExitCode: number;
|
||||
expectedDiagnosticId: string;
|
||||
}>);
|
||||
type GateDefinition = Readonly<{
|
||||
name: string;
|
||||
steps: readonly GateStep[];
|
||||
@@ -40,6 +49,8 @@ if (!gateId || !gate) {
|
||||
|
||||
const output: string[] = [];
|
||||
let passed = true;
|
||||
const DEFAULT_STEP_TIMEOUT_MS = 30 * 60 * 1_000;
|
||||
const MAX_STEP_OUTPUT_BYTES = 16 * 1024 * 1_024;
|
||||
|
||||
const gateEnvironment = { ...process.env };
|
||||
if (gateEnvironment.CI === "true") {
|
||||
@@ -92,19 +103,49 @@ if (passed) {
|
||||
const result = spawnSync(
|
||||
"corepack",
|
||||
["pnpm", step.script, ...(step.args ?? [])],
|
||||
{ encoding: "utf8", env: gateEnvironment },
|
||||
{
|
||||
encoding: "utf8",
|
||||
env: gateEnvironment,
|
||||
timeout: step.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS,
|
||||
maxBuffer: MAX_STEP_OUTPUT_BYTES,
|
||||
},
|
||||
);
|
||||
const stdout = result.stdout ?? "";
|
||||
const stderr = result.stderr ?? "";
|
||||
output.push(
|
||||
`$ corepack pnpm ${step.script} ${(step.args ?? []).join(" ")}`.trim(),
|
||||
result.stdout,
|
||||
result.stderr,
|
||||
stdout,
|
||||
stderr,
|
||||
);
|
||||
const exitedSuccessfully = result.status === 0;
|
||||
const expectationMet =
|
||||
step.expect === "pass" ? exitedSuccessfully : !exitedSuccessfully;
|
||||
if (!expectationMet) {
|
||||
const expectation =
|
||||
step.expect === "pass"
|
||||
? ({ kind: "pass" } as const)
|
||||
: ({
|
||||
kind: "fail",
|
||||
expectedExitCode: step.expectedExitCode,
|
||||
expectedDiagnosticId: step.expectedDiagnosticId,
|
||||
} as const);
|
||||
const classification = classifyGateStepResult(expectation, {
|
||||
status: result.status,
|
||||
signal: result.signal,
|
||||
stdout,
|
||||
stderr,
|
||||
...(result.error
|
||||
? { error: { code: (result.error as NodeJS.ErrnoException).code } }
|
||||
: {}),
|
||||
});
|
||||
output.push(`classification: ${classification.kind}`);
|
||||
if (!classification.expectationMet) {
|
||||
output.push(
|
||||
`expectation failed: expected ${step.expect}, exit=${result.status}`,
|
||||
`expectation failed: expected ${step.expect}, exit=${result.status}, signal=${result.signal ?? "none"}`,
|
||||
...(step.expect === "fail"
|
||||
? [
|
||||
`expected negative fixture identity: exit=${step.expectedExitCode}, diagnostic=${JSON.stringify(step.expectedDiagnosticId)}`,
|
||||
]
|
||||
: []),
|
||||
...(classification.kind === "INFRASTRUCTURE_FAILURE"
|
||||
? [`infrastructure failure: ${classification.detail}`]
|
||||
: []),
|
||||
);
|
||||
passed = false;
|
||||
break;
|
||||
@@ -187,11 +228,61 @@ function parseGateSteps(value: unknown, gateId: string): GateStep[] {
|
||||
const args =
|
||||
candidate.args === undefined
|
||||
? undefined
|
||||
: parseStringArray(candidate.args, `${gateId}[${index}].args`);
|
||||
return {
|
||||
: parseStringArray(candidate.args, `${gateId}[${index}].args`);
|
||||
const timeoutMs = candidate.timeoutMs;
|
||||
if (
|
||||
timeoutMs !== undefined &&
|
||||
(typeof timeoutMs !== "number" ||
|
||||
!Number.isSafeInteger(timeoutMs) ||
|
||||
timeoutMs < 1_000 ||
|
||||
timeoutMs > 3_600_000)
|
||||
) {
|
||||
throw new TypeError(`Invalid CI gate step timeout: ${gateId}[${index}]`);
|
||||
}
|
||||
const base = {
|
||||
script: candidate.script,
|
||||
expect: candidate.expect,
|
||||
...(args ? { args } : {}),
|
||||
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
||||
};
|
||||
if (candidate.expect === "pass") {
|
||||
if (
|
||||
candidate.expectedExitCode !== undefined ||
|
||||
candidate.expectedDiagnosticId !== undefined
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Passing CI gate step cannot declare failure identity: ${gateId}[${index}]`,
|
||||
);
|
||||
}
|
||||
return { ...base, expect: "pass" as const };
|
||||
}
|
||||
if (
|
||||
typeof candidate.expectedExitCode !== "number" ||
|
||||
!Number.isSafeInteger(candidate.expectedExitCode) ||
|
||||
candidate.expectedExitCode < 1 ||
|
||||
candidate.expectedExitCode > 255
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Invalid expected failure exit code: ${gateId}[${index}]`,
|
||||
);
|
||||
}
|
||||
const expectedDiagnosticId = candidate.expectedDiagnosticId;
|
||||
if (
|
||||
typeof expectedDiagnosticId !== "string" ||
|
||||
expectedDiagnosticId.trim().length === 0 ||
|
||||
expectedDiagnosticId.length > 256 ||
|
||||
["\r", "\n", "\0"].some((character) =>
|
||||
expectedDiagnosticId.includes(character),
|
||||
)
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Invalid expected failure diagnostic: ${gateId}[${index}]`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
...base,
|
||||
expect: "fail" as const,
|
||||
expectedExitCode: candidate.expectedExitCode,
|
||||
expectedDiagnosticId,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user