diff --git a/scripts/quality-gate/runner.test.ts b/scripts/quality-gate/runner.test.ts index f4db2237..b120f810 100644 --- a/scripts/quality-gate/runner.test.ts +++ b/scripts/quality-gate/runner.test.ts @@ -4,8 +4,8 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { lanesForMode } from './modes' import { renderMarkdownReport } from './reporter' -import { runQualityGate } from './runner' -import type { QualityGateReport } from './types' +import { runQualityGate, runQualityGateLanes } from './runner' +import type { LaneDefinition, QualityGateReport } from './types' describe('quality gate modes', () => { test('pr mode includes existing path-aware PR checks', () => { @@ -70,6 +70,56 @@ describe('runQualityGate', () => { rmSync(artifactsDir, { recursive: true, force: true }) } }) + + test('continues through later lanes after a failure so reports show full impact', async () => { + const artifactsDir = mkdtempSync(join(tmpdir(), 'quality-gate-test-')) + const lanes: LaneDefinition[] = [ + { + id: 'first-lane', + title: 'First lane', + description: 'Fails intentionally', + kind: 'command', + command: ['false'], + requiredForModes: ['pr'], + }, + { + id: 'second-lane', + title: 'Second lane', + description: 'Still runs', + kind: 'command', + command: ['true'], + requiredForModes: ['pr'], + }, + ] + + try { + const { report } = await runQualityGateLanes({ + mode: 'pr', + dryRun: false, + allowLive: false, + baselineTargets: [], + rootDir: process.cwd(), + artifactsDir, + runId: 'continue-after-failure-test', + }, lanes, async (lane) => ({ + id: lane.id, + title: lane.title, + status: lane.id === 'first-lane' ? 'failed' : 'passed', + command: lane.command, + durationMs: 1, + exitCode: lane.id === 'first-lane' ? 1 : 0, + })) + + expect(report.results.map((result) => result.id)).toEqual(['first-lane', 'second-lane']) + expect(report.summary).toEqual({ + passed: 1, + failed: 1, + skipped: 0, + }) + } finally { + rmSync(artifactsDir, { recursive: true, force: true }) + } + }) }) describe('renderMarkdownReport', () => { diff --git a/scripts/quality-gate/runner.ts b/scripts/quality-gate/runner.ts index f5e6b58c..f03990a4 100644 --- a/scripts/quality-gate/runner.ts +++ b/scripts/quality-gate/runner.ts @@ -7,6 +7,8 @@ import { lanesForMode } from './modes' import { writeReport } from './reporter' import type { LaneDefinition, LaneResult, QualityGateOptions, QualityGateReport } from './types' +type LaneExecutor = (lane: LaneDefinition, options: QualityGateOptions) => Promise + function nowId() { return new Date().toISOString().replace(/[:.]/g, '-') } @@ -140,6 +142,14 @@ function summarize(results: LaneResult[]) { } export async function runQualityGate(options: QualityGateOptions) { + return runQualityGateLanes(options, lanesForMode(options.mode, options.baselineTargets)) +} + +export async function runQualityGateLanes( + options: QualityGateOptions, + lanes: LaneDefinition[], + executeLane: LaneExecutor = runLane, +) { const runId = options.runId ?? nowId() const startedAt = new Date().toISOString() const artifactsRoot = options.artifactsDir ?? join(options.rootDir, 'artifacts', 'quality-runs') @@ -148,12 +158,9 @@ export async function runQualityGate(options: QualityGateOptions) { const runOptions = { ...options, runId, runOutputDir: outputDir } const results: LaneResult[] = [] - for (const lane of lanesForMode(options.mode, options.baselineTargets)) { - const result = await runLane(lane, runOptions) + for (const lane of lanes) { + const result = await executeLane(lane, runOptions) results.push(result) - if (result.status === 'failed') { - break - } } const report: QualityGateReport = {