diff --git a/apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs b/apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs index 2698de093..a4a4cde9e 100644 --- a/apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs +++ b/apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs @@ -447,7 +447,12 @@ async function runWithConcurrency(shards, runner) { return results; } -function extractFailingTestNames(result) { +// 只认**本片名单里真实存在**的用例名:先按 `---- stdout ----` / 失败清单行归一化, +// 再拿名单做精确匹配。不要再用名字前缀白名单猜用例(历史上只放行 `tests::` 与 +// `process_session::tests::`,而 bin 目标的用例名形如 `agent::thread_manager::tests::…`, +// 于是绝大多数用例永远进不了复核,偶发假红就直接判红)。 +function extractFailingTestNames(result, shardTestNames) { + const known = new Set(shardTestNames); const names = new Set(); for (const line of result.failures) { const normalized = line @@ -457,11 +462,8 @@ function extractFailingTestNames(result) { .replace(/\s*stdout\s*----$/, '') .replace(/\s*\(\d+\)\s*$/, '') .trim(); - const match = normalized.match( - /^(process_session::tests::[A-Za-z0-9_:]+|tests::[A-Za-z0-9_:]+)$/, - ); - if (match) { - names.add(match[1]); + if (known.has(normalized)) { + names.add(normalized); } } return [...names]; @@ -518,9 +520,7 @@ async function main() { } // 片内串行的时序型用例在高负载 CI 上会偶发假红。只对失败用例做一次 // 有界复核:复核通过按 flaky 记录,复核失败才判红,避免把真实回归洗掉。 - const failingTestNames = extractFailingTestNames(result).filter((name) => - shardTestNames.includes(name), - ); + const failingTestNames = extractFailingTestNames(result, shardTestNames); if (failingTestNames.length === 0) { return result; } @@ -531,10 +531,13 @@ async function main() { failingTestNames, ); if (!retry.ok) { + // 复核也失败就判红,但**两次的现场都留**:首次是整片的上下文,复核只跑失败用例, + // 丢掉首次现场会让"到底整片跑成什么样"这件事在日志里消失。 return { ...result, failures: [ `re-run of ${failingTestNames.length} failing test(s) also failed`, + ...result.failures, ...retry.failures, ], }; diff --git a/apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs b/apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs index cf86a6a60..9eec73d7d 100644 --- a/apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs +++ b/apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs @@ -68,3 +68,25 @@ test('successful shard keeps its compact summary', (t) => { assert.match(result.output, /shard 1\/1 ok: 1 test\(s\)/); assert.doesNotMatch(result.output, /test passing_case \.\.\. ok/); }); + +// bin 目标的用例名带命名空间(`agent::thread_manager::tests::…`)。复核名单必须靠 +// 「本片名单精确匹配」认用例,不能靠名字前缀白名单,否则这类用例永远进不了重跑。 +test('namespaced failing test is re-run once before the shard is judged', (t) => { + const result = runFixture( + t, + ` +mod agent { + pub mod thread_manager { + #[test] + fn flaky_case() { + assert_eq!(1, 2, "namespaced panic evidence"); + } + } +} +`, + ); + assert.equal(result.status, 1, result.output); + assert.match(result.output, /re-run of 1 failing test\(s\) also failed/); + assert.match(result.output, /agent::thread_manager::flaky_case/); + assert.match(result.output, /namespaced panic evidence/); +});