mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
885c1486bb
* test(ci): single-retry policy for enumerated contention-flaky files * fix: satisfy fallow * test(ci): read failures through a lane reporter so timeouts stay distinguishable * test(ci): cover the lane reporter and drop its duplicated boilerplate * chore(fallow): own the retry lane's tool-loaded export seams * test(ci): block retries on non-test failures and classify timeouts structurally * test(ci): decide retry eligibility from runner metadata and route gate verdicts through blockers * refactor(ci): name the retry policy's rules in code instead of comments * test(ci): mark runner-aborted timeouts inside the runner instead of inferring them * test(ci): make timeout provenance a per-run secret, not a writable flag Cover direct task.meta mutation in the real child-Vitest fixture gate. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(ci): retry the failed files in the first run's project and coverage modes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: drop deleted repo-health file from the retry list after #1480 Rebase onto main post-#1480: the SkillGym/repo-health descope deleted scripts/repo-health/run.test.ts, whose CONTENTION_RETRY_FILES entry would now fail this PR's own missing-file check, and inlined the slow-test budgets into the reporter, resolving the budgets-module import. Envelope comments now point at scripts/lib/lane-envelope.ts instead of the closed #1430. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
76 lines
2.9 KiB
TypeScript
76 lines
2.9 KiB
TypeScript
// Failure sink for the contention single-retry policy (#1419): writes each
|
|
// failed test with its retry eligibility, plus every failure that is not a
|
|
// failed test case. Enabled by `CONTENTION_RETRY_FAILURES`; module ids are
|
|
// written as-is and `parseFailureReport` makes them repo-relative.
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import type { Reporter, TestCase, TestModule } from 'vitest/node';
|
|
import { isRunnerTimeout, type RunBlocker, type TestFailure } from './contention-retry.ts';
|
|
import { drainRunBlockers } from './run-blocker-bus.ts';
|
|
import { RUNNER_TIMEOUT_TOKEN_ENV } from './runner-timeout-meta.ts';
|
|
|
|
export const FAILURE_FILE_ENV = 'CONTENTION_RETRY_FAILURES';
|
|
|
|
export type FailureReport = { failures: TestFailure[]; blockers: RunBlocker[] };
|
|
|
|
/** The failed-test view of a Vitest test case, or null when it did not fail. */
|
|
export function failedTestCase(testCase: TestCase, token: string | undefined): TestFailure | null {
|
|
const result = testCase.result();
|
|
if (result.state !== 'failed') return null;
|
|
const errors = result.errors ?? [];
|
|
return {
|
|
file: (testCase.module as TestModule).moduleId,
|
|
testName: testCase.fullName,
|
|
message: errors.map((error) => `${error.name}: ${error.message}`).join('\n'),
|
|
timeout: isRunnerTimeout(errors, testCase.meta(), token),
|
|
};
|
|
}
|
|
|
|
/** Failures that rerunning the failed files cannot re-check. */
|
|
export function runBlockers(
|
|
testModules: readonly TestModule[],
|
|
unhandledErrors: readonly { name?: unknown; message?: unknown }[],
|
|
): RunBlocker[] {
|
|
const blockers: RunBlocker[] = drainRunBlockers();
|
|
for (const error of unhandledErrors) {
|
|
blockers.push({
|
|
kind: 'unhandled error',
|
|
detail:
|
|
`${String(error.name ?? 'Error')}: ${String(error.message ?? '')}`.split('\n')[0] ?? '',
|
|
});
|
|
}
|
|
for (const testModule of testModules) {
|
|
for (const error of testModule.errors()) {
|
|
blockers.push({
|
|
kind: 'module error',
|
|
detail: `${testModule.moduleId}: ${String(error.message ?? '').split('\n')[0] ?? ''}`,
|
|
});
|
|
}
|
|
}
|
|
return blockers;
|
|
}
|
|
|
|
export function writeFailureReport(report: FailureReport, target: string): void {
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.writeFileSync(target, `${JSON.stringify(report)}\n`);
|
|
}
|
|
|
|
export default function contentionRetryReporter(): Reporter {
|
|
const failures: TestFailure[] = [];
|
|
return {
|
|
onTestCaseResult(testCase: TestCase): void {
|
|
const failed = failedTestCase(testCase, process.env[RUNNER_TIMEOUT_TOKEN_ENV]);
|
|
if (failed) failures.push(failed);
|
|
},
|
|
onTestRunEnd(
|
|
testModules: readonly TestModule[],
|
|
unhandledErrors: readonly { name?: unknown; message?: unknown }[],
|
|
): void {
|
|
const target = process.env[FAILURE_FILE_ENV];
|
|
if (!target) return;
|
|
writeFailureReport({ failures, blockers: runBlockers(testModules, unhandledErrors) }, target);
|
|
},
|
|
};
|
|
}
|