From 00f2a701bd02fe7581251d48038b1ef1091e79a3 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Fri, 28 Aug 2026 04:44:22 +0800 Subject: [PATCH] test(scripts): align the translation-pairing-merge budget with the coverage lane Every case in the suite drives real git invocations against a scratch repository, so it is bound by process creation rather than by its assertions. The describe-level 15 s capped all 23 cases below the 90 s the Windows coverage lane passes as --testTimeout, and the suite has been observed timing out at 15000ms on a branch that did not touch the file. Refs #2677. --- ...translation-pairing-merge-budget.i18n.yaml | 6 ++++ ...-08-27-translation-pairing-merge-budget.md | 31 +++++++++++++++++++ ...-27-translation-pairing-merge-budget.zh.md | 31 +++++++++++++++++++ scripts/translation-pairing-merge.spec.ts | 10 +++++- 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 .agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.i18n.yaml create mode 100644 .agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.md create mode 100644 .agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.zh.md diff --git a/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.i18n.yaml b/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.i18n.yaml new file mode 100644 index 0000000000..f92bcc8e12 --- /dev/null +++ b/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.md +2026-08-27-translation-pairing-merge-budget.md: 296e71fbbebf136602e02d2d8d64dbbfd8a336a3 +2026-08-27-translation-pairing-merge-budget.zh.md: 8bdac5902301d7105b7851e3c446a9a327b69819 diff --git a/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.md b/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.md new file mode 100644 index 0000000000..296e71fbbe --- /dev/null +++ b/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.md @@ -0,0 +1,31 @@ +# Agent Note: Coverage-lane budget for the translation-pairing-merge suite + +Status: implemented + +English | [中文](2026-08-27-translation-pairing-merge-budget.zh.md) + +## Problem + +[`scripts/translation-pairing-merge.spec.ts`](../../../../scripts/translation-pairing-merge.spec.ts) took a `describe`-level `{ timeout: 15_000 }`. All 23 of its cases inherit that value; none carries an allowance of its own. + +Every case builds a scratch repository and drives it through spawned `git` invocations, so the suite is bound by process creation rather than by its assertions. On the self-hosted Windows runners all instances share one volume, and process creation there shows occasional multi-second spikes rather than a uniform slowdown. Under that contention this suite has been observed reporting `Test timed out in 15000ms` on a branch that did not touch the file, so the budget rather than the change under test decided the outcome. + +## Decision + +The suite takes `{ timeout: 90_000 }`, matching `DSH_COVERAGE_TEST_TIMEOUT_MS` in [`.github/workflows/ci.yml`](../../../../.github/workflows/ci.yml), which the Windows coverage lane passes as `--testTimeout`. + +A `describe` value takes precedence over that flag rather than deferring to it. A smaller one therefore lowers what the lane already grants, and because no case here carries its own allowance, every one of the 23 was capped at 15 s while the lane offered 90 s. + +## Consequences + +The suite tolerates a multi-second `git` spawn spike on the shared-volume runners and defers to the budget the coverage lane provides. The value is not a measurement of how long these cases need: the slowest three complete in roughly 0.7-1.2 s depending on the host, and raising the ceiling does not slow a passing run. + +A raised ceiling does not weaken the assertions: with the budget raised sixfold a suite still fails through its own assertions rather than through a timeout, because the ceiling only decides when waiting stops. It does widen what counts as acceptable duration, so a real slowdown from a few hundred milliseconds to tens of seconds now passes where the previous 15 s would have caught it. That detection is traded away deliberately: the 15 s ceiling was firing on contention rather than on regressions, so what it caught was the shared volume, not the code. + +## Alternatives considered + +**Raise `testTimeout` for the whole unit lane.** That would change every suite in the repository to fix one whose cost is specific to spawning `git`. + +**Give each case its own allowance.** Twenty-three separate values restate one property of the machine, and a later case added without one would silently inherit the lower ceiling again. + +**Leave the value and retry on failure.** A retry moves the failure to another case or another run and leaves a red gate that carries no information about the code under test. diff --git a/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.zh.md b/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.zh.md new file mode 100644 index 0000000000..8bdac59023 --- /dev/null +++ b/.agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.zh.md @@ -0,0 +1,31 @@ +# Agent Note: translation-pairing-merge 套件的 coverage lane 预算 + +Status: implemented + +[English](2026-08-27-translation-pairing-merge-budget.md) | 中文 + +## 问题 + +[`scripts/translation-pairing-merge.spec.ts`](../../../../scripts/translation-pairing-merge.spec.ts) 在 `describe` 层加了 `{ timeout: 15_000 }`。它的 23 个用例全部继承这个值,没有任何一个自带余量。 + +每个用例都会建一个临时仓库并通过 spawn 的 `git` 驱动它,因此这个套件受进程创建约束,而不是受它的断言约束。在自托管 Windows runner 上所有实例共用一个卷,而那里的进程创建表现为偶发的数秒尖峰,不是均匀变慢。在那种争抢下,这个套件曾在一个没有改动该文件的分支上报出 `Test timed out in 15000ms`,也就是说决定结果的是预算而不是被测改动。 + +## 决定 + +套件取 `{ timeout: 90_000 }`,与 [`.github/workflows/ci.yml`](../../../../.github/workflows/ci.yml) 里的 `DSH_COVERAGE_TEST_TIMEOUT_MS` 一致,Windows 覆盖率 lane 把它作为 `--testTimeout` 传入。 + +`describe` 层的取值优先于那个 flag,而不是让位于它。所以更小的值会压低 lane 已经给出的预算;又因为这里没有任何用例自带余量,23 个用例全部被限制在 15 秒,而 lane 提供的是 90 秒。 + +## 后果 + +套件能容忍共享卷 runner 上一次数秒的 `git` spawn 尖峰,并让位于 coverage lane 提供的预算。这个值不是对「这些用例需要多久」的测量:最慢的三个用例视主机而定约为 0.7–1.2 秒,而抬高上限不会让一次通过的运行变慢。 + +抬高上限不会削弱断言:把预算抬到六倍之后,套件仍然通过它自己的断言失败而不是通过超时失败,因为上限只决定何时停止等待。但它确实放宽了「多长算可接受」——一个从几百毫秒退化到几十秒的真实变慢现在会通过,而此前的 15 秒会拦住它。这项检测能力是有意换掉的:15 秒上限触发的是争抢而不是回归,所以它拦住的是共享卷,不是代码。 + +## 备选方案 + +**给整个 unit lane 抬高 `testTimeout`。** 那会为了修一个成本特定于 spawn `git` 的套件而改变仓库里的每一个套件。 + +**给每个用例各自加余量。** 23 个分散的取值重复表达同一个机器属性,而后续新增的用例若没写,又会静默继承较低的上限。 + +**保留取值、失败时重跑。** 重跑只是把失败挪到另一个用例或另一次运行,同时留下一个不携带被测代码信息的红灯。 diff --git a/scripts/translation-pairing-merge.spec.ts b/scripts/translation-pairing-merge.spec.ts index 7293989c2e..91c84b2eca 100644 --- a/scripts/translation-pairing-merge.spec.ts +++ b/scripts/translation-pairing-merge.spec.ts @@ -261,7 +261,15 @@ function expectMergedPair(fixture: Fixture): void { ) } -describe('translation pairing merge composition', { timeout: 15_000 }, () => { +// Every case in this suite drives real `git` invocations against a scratch +// repository, so it is bound by process creation rather than by its assertions. +// The value matches DSH_COVERAGE_TEST_TIMEOUT_MS, which the Windows coverage +// lane passes as --testTimeout: a describe value overrides that flag rather than +// yielding to it, so a smaller one here lowers what the lane grants every case +// in this file, none of which carries an allowance of its own. Measurements and +// the rejected alternatives are in +// .agents/notes/implemented/testing/2026-08-27-translation-pairing-merge-budget.md. +describe('translation pairing merge composition', { timeout: 90_000 }, () => { it('rejects a pairing-record path outside the repository', () => { const fixture = createFixture(false)