Skip to content
2 changes: 0 additions & 2 deletions scripts/tests/install-script.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ if (process.env.CI && process.platform !== 'win32' && !zipAvailable) {
const itWithZip = zipAvailable ? it : it.skip;
const itOnUnixWithZip = zipAvailable ? itOnUnix : it.skip;

vi.setConfig({ testTimeout: 30_000 });

describe('installation scripts', () => {
it('keeps the Linux/macOS installer lightweight', () => {
const script = readScript(
Expand Down
18 changes: 9 additions & 9 deletions scripts/tests/qwen-autofix-workflow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2064,7 +2064,7 @@ describe('qwen-autofix workflow', () => {
head: H,
}).stale,
).toBe(false);
}, 30000);
});

it('behaviorally replays the eligibility recheck across lifecycle and label states', () => {
// Extract the recheck VERBATIM (drift fails the test) and run it with a
Expand Down Expand Up @@ -12643,7 +12643,7 @@ exit 1
/then\n\s+echo "📊 milestone digest posted/,
);
expect(pushAndReportStep).toContain('milestone digest failed to post');
}, 30000);
});

it('salvages a race-lost push by merging the moved head instead of discarding the run', () => {
// A one-shot push dies `fetch first` whenever anything pushes to the PR
Expand Down Expand Up @@ -13674,7 +13674,7 @@ exit 1
const fuzz = run(crossWorkspace, { enforce: 'terminate' });
expect(fuzz.out).toContain('SURVIVED');
expect(fuzz.advisory).toContain('outside the PR footprint');
}, 30000);
});

it('upserts deferred findings into a per-PR issue that survives the merge', () => {
// Wiring: the upsert runs after both shared resolve/reply call sites
Expand Down Expand Up @@ -17513,7 +17513,7 @@ exit 1
expect(ciWorkflow).toContain(
'.github/scripts/autofix-status-heartbeat.test.mjs',
);
}, 30000);
});

it('renders the whole managed fleet into the run summary', () => {
// Diagnosing a stall used to mean listing bot PRs, regexing each one's eval
Expand Down Expand Up @@ -21277,7 +21277,7 @@ exit 0
expect(runAddressReview(dir, stub).status).not.toBe(0);
expect(existsSync(join(dir, 'agent-api-error'))).toBe(false);
});
}, 30000);
});

it('classifies permanent API failures terminal and records the cause class', () => {
// A permanent 400 whose text happens to carry a 3-digit number in 500-599
Expand Down Expand Up @@ -23960,10 +23960,10 @@ describe('review verification gate: baseline A/B on deterministic rejection', ()
expect(neutralized.stdout).toContain(';;error;;forged');
expect(neutralized.stdout).not.toContain('::error::forged');
// Eight runGate arms, each a fixture repo plus a full gate-script
// replay under bash — this outgrows the 5s default on slow runners
// (it timed out at ~6.4s on the PR head); the suite's convention is
// an explicit per-test budget for tests that spawn subprocesses.
}, 30000);
// replay under bash — this outgrows vitest's 5s default on slow
// runners (it timed out at ~6.4s on the PR head), so it runs on the
// suite ceiling in scripts/tests/vitest.config.ts.
});

it('rejects a handoff written over a dirty workspace, non-retryably', () => {
// A handoff claims the round deliberately changed NOTHING; dirt beside
Expand Down
34 changes: 31 additions & 3 deletions scripts/tests/unit-vitest-configs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { readFileSync } from 'node:fs';
import { readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it, vi } from 'vitest';

Expand Down Expand Up @@ -227,8 +228,9 @@ describe('scripts suite timeout', () => {
// 30s was the quiet-host figure. Release run 33725742855 lost its Quality
// Checks (Scripts) job to two files at once — qwen-autofix-workflow, whose
// heaviest case measures ~14s idle, and acp-serve-boundary-guard — neither
// slow, both past 30s under contention. A per-file `vi.setConfig` cannot
// fix it: these cases register their timeout at collection.
// slow, both past 30s under contention. A per-file `vi.setConfig` does
// outrank this config, but not the per-test budget the cases that timed out
// carry: that is registered at collection and beats both.
for (const [stub, expected] of [
[undefined, 90_000],
['5000', 5_000],
Expand All @@ -245,4 +247,30 @@ describe('scripts suite timeout', () => {
vi.unstubAllEnvs();
}
});

it('leaves no file-level runtime override shadowing that ceiling', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R4-2: This guard test is named leaves no file-level runtime override shadowing that ceiling, but it scans only for vi.setConfig(...testTimeout...) — while per-test timeout arguments (it(name, fn, timeout)) also outrank the config ceiling, and that is the exact override shape this PR removes seven instances of from scripts/tests/qwen-autofix-workflow.test.js (the }, 30000); endings). A revert or merge-conflict resolution restoring it('...', fn, 30000) re-opens the #10853 timeout class on ecs-qwen-* runners while this test reads green — the guard cannot catch a regression of the very hunks this PR ships. Either extend the scan to numeric per-test ceilings over the same surface (asserting none reaches the flat ceiling), or narrow the test's name/comment to state it detects only vi.setConfig overrides.

Witness:

semantics probe: config testTimeout 10_000, test with a 300ms third argument sleeping 2s
  -> x Test timed out in 300ms.   (the per-test argument outranks the config)
guard probe: planted collected file ending `}, 30000);` under scripts/tests/
  -> Tests 28 passed (28)   (the guard stays green)

Any extended scan must keep the pre-existing intentional per-test budgets passing: }, 20000); at scripts/tests/qwen-autofix-workflow.test.js:1543, }, 10000); at :11694 and :21601, }, 60_000); at scripts/tests/brand-create-safety.test.js:256, }, 40000); at scripts/tests/check-tui-dep-direction.test.js:573. If the scan is extended, prove it by mutation: restore any one of the }, 30000); endings this PR removed from scripts/tests/qwen-autofix-workflow.test.js — this test must fail naming that file.

中文说明

R4-2:该守卫测试名为 leaves no file-level runtime override shadowing that ceiling,但它只扫描 vi.setConfig(...testTimeout...)——而单测试超时参数(it(name, fn, timeout))同样优先于配置上限,且那正是本 PR 从 scripts/tests/qwen-autofix-workflow.test.js 中移除的七处覆盖形式(}, 30000); 结尾)。一次回退或合并冲突解决若恢复 it('...', fn, 30000),就会在 ecs-qwen-* runner 上重新引发 #10853 的超时类失败,而该测试仍显示绿色——守卫无法捕获本 PR 所提交改动自身的回归。要么把扫描扩展到同一面上的数值型单测试上限(断言没有任何一个达到固定上限),要么收窄测试名/注释,声明它只检测 vi.setConfig 覆盖。

证据(Witness)见英文部分代码块。

任何扩展扫描都必须让既有的、有意写下的单测试预算继续通过:scripts/tests/qwen-autofix-workflow.test.js:1543}, 20000);、:11694 与 :21601 的 }, 10000);scripts/tests/brand-create-safety.test.js:256}, 60_000);scripts/tests/check-tui-dep-direction.test.js:573}, 40000);。如果扩展扫描,请用变异证明:恢复本 PR 从 scripts/tests/qwen-autofix-workflow.test.js 移除的任意一个 }, 30000); 结尾——该测试必须失败并指名该文件。

— qwen3.8-max via Qwen Code /review (v0.22.3)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not applied this round — same reason as R4-1: this guard test cannot survive a merge with main.

The finding is correct and my own probe agrees with the semantics you measured: a file-level vi.setConfig({ testTimeout: 100 }) beats a 3000ms suite ceiling (Test timed out in 100ms), and per-test third-argument timeouts outrank it too. So the name does promise more than a vi.setConfig-only scan delivers.

It is moot on this branch: origin/main already replaced the RUNNER_NAME ternary this guard pins with Number(process.env['QWEN_SCRIPTS_TEST_TIMEOUT_MS'] ?? 90_000), and this PR's pin asserting toBe(60_000) fails against it. Neither extending the scan nor narrowing the name is worth doing on a test the merge deletes.

For #10910, the substance of this finding is live on main in a form neither the guard nor #10910's described scope covers: scripts/tests/install-script.test.js:63 still carries a file-level vi.setConfig({ testTimeout: 30_000 }); that provably shadows main's 90s ceiling, pinning that file at the old flat 30s on the shared ecs-qwen-* pool. This PR removes that line; #10910 is described as removing the six per-test }, 30000) ceilings in qwen-autofix-workflow.test.js, which is a different file and a different override shape. Suggest folding the one-line removal in before this PR closes.

Thread left open.


本轮未应用 —— 与 R4-1 同理:该守卫测试无法在与 main 的合并中存活。

该发现正确,我自己的探测也与你测到的语义一致:文件级 vi.setConfig({ testTimeout: 100 }) 会压过 3000ms 的套件上限(Test timed out in 100ms),单测试第三参数超时同样优先。所以测试名的承诺确实超过只扫 vi.setConfig 所能兑现的范围。

但在本分支上已无意义:origin/main 已经用 Number(process.env['QWEN_SCRIPTS_TEST_TIMEOUT_MS'] ?? 90_000) 替换了本守卫所钉的 RUNNER_NAME 三元表达式,而本 PR 断言 toBe(60_000) 的 pin 对它失败。无论是扩展扫描还是收窄测试名,都不值得花在一个合并会删掉的测试上。

#10910 而言,这条发现的实质内容正以守卫和 #10910 描述范围都未覆盖的形式存活在 main 上:scripts/tests/install-script.test.js:63 仍带有文件级 vi.setConfig({ testTimeout: 30_000 });,可证明地遮蔽了 main 的 90s 上限,把该文件在共享 ecs-qwen-* 机器池上钉回旧的固定 30s。本 PR 删除了这一行;而 #10910 被描述为移除 qwen-autofix-workflow.test.js 中六处单测试 }, 30000) 上限 —— 那是另一个文件、另一种覆盖形式。建议在本 PR 关闭前把这一行的删除并进去。

Thread 保持 open。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R4-2: Still stands at the post-merge head — this guard test is named leaves no file-level runtime override shadowing that ceiling, but it scans only vi.setConfig(...testTimeout...) while per-test timeout arguments (it(name, fn, timeout)) outrank the project config identically — this PR itself removes six of them from qwen-autofix-workflow.test.js for exactly that reason. The round-4 reply declined this finding because the guard "cannot survive a merge with main"; the merge kept the guard, so the finding is live again.

Confirmed by probe (the guard's own scan, executed):

ARM c — per-test 50ms cap on a 300ms body under the 90s ceiling:
  → Test timed out in 50ms.          ← per-test caps shadow the ceiling too
ARM d — same test, uncapped:
  Tests 1 passed (1)
planted  it('spawn-heavy case', async () => {}, 30_000);  in the scan surface:
  → guard: Tests 1 passed | 27 skipped (green)             ← mutant survives

The shape is live at HEAD — 8 instances, including in the very file this diff de-shadows: install-script.test.js:1872 and :1924 (per-test 30_000 caps on the tar+gzip packaging cases); also qwen-triage-workflow.test.js:6372/6652/7096 and upload-aliyun-oss-assets.test.js:182/224/249.

Failure scenario: one of the spawn-heavy capped cases lands on a contended release host and needs more than its stale cap — it times out at 30s while the 90s ceiling was raised precisely to shelter it; Quality Checks (Scripts) goes red again, the exact incident class this PR fixes, while this guard stays green.

Suggested fix: either extend the guard to per-test timeout arguments (a scan anchored to the test-call closer shape), or delete the residual sub-ceiling caps the same way the six , 30000) ceilings were deleted — they can only ever clamp below the 90s ceiling now. If per-test caps are deliberately allowed, say so in the guard's comment so the coverage boundary is explicit instead of implied by the name.

Fix constraint: scanned files contain fixture strings reproducing the un-anchored closer form — 'setTimeout(() => {}, 30_000);' at qwen-autofix-workflow.test.js:21676 and upload-aliyun-oss-assets.test.js:96 — so a text scan must anchor to the test-call shape rather than matching bare }, <number>);.

Fix witness: after the change, planting it('x', () => {}, 30000); in any scanned file must make the guard test fail.

中文说明

[Suggestion] R4-2:在合并后的 HEAD 上仍然成立 —— 该守卫测试名为 leaves no file-level runtime override shadowing that ceiling,但它只扫描 vi.setConfig(...testTimeout...),而单测试超时参数(it(name, fn, timeout))同样优先于项目配置 —— 本 PR 自己就因此从 qwen-autofix-workflow.test.js 删除了六个这类上限。第 4 轮的答复以"守卫无法在与 main 合并后存活"为由未采纳;合并后守卫被保留,因此该发现重新生效。

探针确认(执行的是守卫自己的扫描逻辑):在 90 秒上限下给 300ms 测试体加单测试 50ms 上限 → Test timed out in 50ms.(单测试上限同样遮蔽上限);无上限对照组通过。在扫描面植入 it('spawn-heavy case', async () => {}, 30_000); → 守卫绿色(变异体存活)。

该形态在当前 HEAD 真实存在 —— 共 8 处,包括本 diff 刚移除遮蔽的那个文件:install-script.test.js:1872:1924(tar+gzip 打包用例上的单测试 30_000 上限);另有 qwen-triage-workflow.test.js:6372/6652/7096upload-aliyun-oss-assets.test.js:182/224/249

失败场景:某个拉子进程的重用例落在有争用的发布机器上、耗时超过其陈旧上限时,会在 90 秒上限本是为保护它而抬高的情况下于 30 秒超时 —— Quality Checks (Scripts) 再次变红,正是本 PR 修复的事故类别,而守卫保持绿色。

建议修复:或把守卫扩展到单测试超时参数(以测试调用收尾形状为锚的扫描),或像删除六个 , 30000) 上限那样删除残留的低于上限的单测试上限 —— 它们现在只可能把上限往下压。如果单测试上限是刻意保留的,请在守卫注释里说明,让覆盖边界显式化,而不是由测试名暗示。

修复约束:被扫描文件中存在复现未锚定收尾形式的夹具字符串 —— qwen-autofix-workflow.test.js:21676 与 upload-aliyun-oss-assets.test.js:96 的 'setTimeout(() => {}, 30_000);' —— 因此文本扫描必须锚定到测试调用形状,而不是匹配裸的 }, <number>);

修复见证:修改后,在任何被扫描文件植入 it('x', () => {}, 30000); 必须使守卫测试失败。

— qwen3.8-max via Qwen Code /review (v0.23.0)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partly implemented, sweep deferred — leaving this thread open.

Implemented: the coverage boundary is now explicit in the guard's comment instead of implied by its name — "Deliberate per-test budgets (it(name, fn, ms)) are out of scope: they are visible at the test they clamp, unlike a file-wide override." That is the third remedy this finding offered.

Premise reproduced first, on this suite's own config (vitest 3.2.7, 90s ceiling): a per-test cap of 50 on a 300ms body fails Test timed out in 50ms., and a planted it('x', () => {}, 30_000) left the guard green. Per-test caps do shadow the ceiling identically, and the guard was blind to them.

Not implemented — neither extending the guard nor deleting the caps:

  • The census is ~4.75x what the finding reported. An anchored scan of this head counts 38 per-test caps across 8 files, not 8 across 3: check-tui-dep-direction 15, install-script 8 (not 2 — 1872/1924 are the 30_000 pair named here; six more 15000 multi-line caps sit at 3010/3134/3578/3616/3731/3764), qwen-triage-workflow 4, brand-create-safety 3, qwen-autofix-workflow 3, upload-aliyun-oss-assets 3, lint 1, qwen-repo-hygiene-workflow 1. Every value is 10s–60s, so every one clamps below the 90s ceiling.
  • Deleting them touches six files this PR never opened and outweighs its entire diff. Several are not stale pins but budgets their authors chose deliberately — 15 uniform 40000 caps in one suite, 3x 60_000 in another — and three (upload-aliyun-oss-assets 182/224/249) are already disclosed as out of scope in the PR body and were declined in an earlier round against measured headroom (23% and 40% of ceiling in the real run).
  • Extending the guard is not cheap either, and this finding's own fix constraint is the blocker: scanned files carry fixture strings reproducing the un-anchored closer form ('setTimeout(() => {}, 30_000);'), so a text scan must anchor to the test-call shape — a mini-parser inside a test guard. Such a guard would also red on all 38 instances the moment it landed, forcing the sweep into the same round.

Recorded in the deferred-findings queue so the residual survives merge instead of being lost. This round removed the two couplings that could be deleted outright (R4-1's argument shape, R4-3's extension list) rather than adding a third scanner beside them.

中文说明

部分实现,清扫延后 —— 本线程保持开放。

已实现:覆盖边界现在在守卫注释中显式写明,而不再由测试名暗示 —— “Deliberate per-test budgets (it(name, fn, ms)) are out of scope: they are visible at the test they clamp, unlike a file-wide override.”(刻意写下的单测试预算不在范围内:它们对其所夹的测试是可见的,不像影响整个文件的覆盖。)这正是本发现提供的第三种修复方案。

先复现前提,用的是本套件自身的配置(vitest 3.2.7,90 秒上限):300ms 测试体加 50 的单测试上限会以 Test timed out in 50ms. 失败;植入 it('x', () => {}, 30_000) 时守卫保持绿色。单测试上限确实同样遮蔽上限,而守卫对此是盲的。

未实现 —— 既未扩展守卫,也未删除这些上限:

  • 普查结果约为该发现所报的 4.75 倍。对该 HEAD 做锚定扫描,数出 8 个文件中共 38 处单测试上限,而不是 3 个文件中 8 处:check-tui-dep-direction 15、install-script 8(不是 2 —— 1872/1924 是此处点名的 30_000 那两处;另有 6 处 15000 多行形式位于 3010/3134/3578/3616/3731/3764)、qwen-triage-workflow 4、brand-create-safety 3、qwen-autofix-workflow 3、upload-aliyun-oss-assets 3、lint 1、qwen-repo-hygiene-workflow 1。取值都在 10 秒到 60 秒之间,因此每一处都夹在 90 秒上限之下。
  • 删除它们会动到本 PR 从未打开过的 6 个文件,体量超过其整个 diff。其中若干并非陈旧钉子,而是作者刻意选定的预算 —— 某个套件里 15 处统一的 40000,另一个里 3 处 60_000 —— 而其中 3 处(upload-aliyun-oss-assets182/224/249)已在 PR 正文中披露为范围外,并在早前一轮依据实测余量(真实运行中为上限的 23% 与 40%)被拒绝。
  • 扩展守卫同样不便宜,而本发现自己提出的修复约束正是障碍:被扫描文件中存在复现未锚定收尾形式的夹具字符串('setTimeout(() => {}, 30_000);'),因此文本扫描必须锚定到测试调用形状 —— 那等于在测试守卫里写一个小型解析器。这样的守卫一落地就会在全部 38 处上变红,从而把清扫强行拉进同一轮。

已记入延后发现队列,使该残留在合并后仍然留存、不至于丢失。本轮移除的是可以整体删掉的两处耦合(R4-1 的参数形状、R4-3 的扩展名列表),而不是在它们旁边再加第三个扫描器。

// The pin above re-imports the config module, so it cannot see an override
// a suite applies to itself: a runtime `setConfig` in a test file outranks
// the project config and holds that suite at its own number while this file
// reads green — install-script.test.js sat at 30s under the raised ceiling
Comment thread
qwen-code-dev-bot marked this conversation as resolved.
// that way. Every argument shape does that, so the call itself is the
// violation; matching `testTimeout` inside the arguments let a parenthesized
// value ahead of it slip past. The walk takes every file here, not the
// include glob's extension list, so widening that list cannot leave the scan
// behind. Deliberate per-test budgets (`it(name, fn, ms)`) are out of scope:
// they are visible at the test they clamp, unlike a file-wide override.
const here = fileURLToPath(new URL('.', import.meta.url));
const shadowing = readdirSync(here, {
recursive: true,
withFileTypes: true,
})
.filter((entry) => entry.isFile())
.filter((entry) =>
/vi\.setConfig\(/.test(
readFileSync(join(entry.parentPath, entry.name), 'utf8'),
),
)
.map((entry) => entry.name);
expect(shadowing).toEqual([]);
});
});
5 changes: 3 additions & 2 deletions scripts/tests/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ export default defineConfig({
// Quality Checks (Scripts) job to two files at once —
// qwen-autofix-workflow.test.js, whose heaviest case measures ~14s idle,
// and acp-serve-boundary-guard.test.js — neither of them slow, both past
// 30s under contention. Per-test `vi.setConfig` does not help: these
// cases register their timeout at collection, before it runs.
// 30s under contention. A per-file `vi.setConfig` does outrank this
// config, but not the per-test budget the cases that timed out carry: that
// is registered at collection and beats both.
testTimeout: Number(process.env['QWEN_SCRIPTS_TEST_TIMEOUT_MS'] ?? 90_000),
coverage: {
provider: 'v8',
Expand Down
Loading