Skip to content

fix(#566): guard the file-scoped test timeout instead of splitting by size - #666

Merged
Weegy merged 2 commits into
mainfrom
fix/566-split-slow-test-files
Aug 12, 2026
Merged

fix(#566): guard the file-scoped test timeout instead of splitting by size#666
Weegy merged 2 commits into
mainfrom
fix/566-split-slow-test-files

Conversation

@Weegy

@Weegy Weegy commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #566

The issue's premise no longer holds

#566 asks to split slotTypecheckPipeline and cliBackendDetector, sized on measurements taken back at PR #550. Re-measured on 3c86f1c8, node 22.22.3, each file run on its own:

File #566 records measured now factor
test/builder/slotTypecheckPipeline.test.ts 18 394 ms 2 032 ms 9.0× faster
test/cliBackendDetector.test.ts 16 506 ms 3 772 ms 4.4× faster
test/devplatform/dockerBackend.test.ts not mentioned 5 163 ms today's actual slowest

Neither named file is the slowest any more, and cliBackendDetector is 175 lines — splitting that into 4–6 files would be churn with nothing behind it.

So this PR ships what the issue is actually about.

What was really missing

--test-timeout is applied to the file, not the leaf. A file whose leaves are each fast but whose total crosses the ceiling is killed as a unit, and the failure is reported against whichever leaf happened to be running — it does not look like "this file grew too big".

The margin went unwatched since #550 sized it, and there is a concrete reason why: the per-file total was not observable at all. Run a glob through the spec or tap reporter and both flatten to suite names — the filename is gone. (Confirmed while building this: the tap output for the full suite contains no file paths.)

test:summary is the one event that carries both file and duration_ms, emitted once per file. A second reporter on the existing npm test records it at no extra cost — the guard must not double the CI time it guards — and scripts/check-test-file-durations.mjs turns the JSON into a gate.

Why a fraction of the ceiling, not a committed baseline

The repo's other ratchets commit an absolute number because they count things that do not vary by machine.

Corrected after seeing the guard run on CI. I first argued durations vary 4.7× by machine, citing ~36 s locally vs ~172 s on the runner. That conflated two different numbers. The aggregate varies 4.7× — but that is parallelism (16-way local vs ~4-way CI), not speed. Per FILE the two environments agree closely: slowest file 5147 ms locally vs 5358 ms on CI, a 4 % difference.

So a committed ms baseline would have been more viable than I claimed. The fraction is still the better fit, for reasons that survive the correction: it needs no re-committing as the suite grows, it cannot go stale, and it states the risk directly — how close the slowest file is to being killed — rather than encoding it indirectly as a number someone must maintain. But the "machines differ too much" argument was wrong, and a reviewer weighing this tradeoff should have the real figures.

  • WARN at 25 % of the ceiling — visible long before it matters
  • FAIL at 50 %
  • The ceiling is parsed out of the test script, not duplicated, so lowering --test-timeout tightens the guard automatically instead of silently widening the gap between two numbers.
Slowest test files (ceiling 120000 ms per FILE, not per test)

          5163 ms    4.3%  test/devplatform/dockerBackend.test.ts
          5022 ms    4.2%  test/orchestrator/promptMaskPipeline.test.ts
          4786 ms    4.0%  test/cliBackendDetector.test.ts
          ...
  572 files, slowest 5163 ms = 23.2x headroom to the 120000 ms ceiling.
✓ No test file is near the per-file timeout.

Confirmed running on CI (job middleware (lint + typecheck + test), step Test file durations): 572 files, slowest dockerBackend.test.ts at 5358 ms = 22.4x headroom. The step is instantaneous — it reads the JSON the test step already wrote.

Mutation check

The guard is proven to fail, not assumed to:

Case Expected Result
real data exit 0
one file at 60 000 ms (50 %) exit 1
one file at 59 999 ms exit 0 + WARN
empty file list exit 1
missing input file exit 1

The last two matter most: a guard that passes while checking nothing is worse than no guard.

Something the measurement turned up that you should see

While building this I hit a real 120 000 ms file timeout on test/devplatform/devPlatformRoutes.test.ts — the exact failure mode #566 describes. It is not file size. In isolation that file runs in 1.77 s, 29/29 green; it only hangs under full-suite concurrency.

I then ran a controlled A/B on this branch, same commit, 3 rounds each at --test-concurrency=4:

result
without the new reporter 1 green, 2 red
with the new reporter 1 green, 2 red

Identical — the reporter is not the cause. But it also means --test-concurrency=4 does not make this suite deterministic on a 16-core machine: 4 of 6 full runs failed, each with a different victim (builderIssueReporting, devPlatformGates, devWebhooks, devPlatformRoutes), the errors being ECONNREFUSED / fetch failed. That is the localhost socket contention #605 suspects, and it is worse than #605's single green run suggested. CI's 4-vCPU runner is consistently green today, so it does not bite there.

This does not affect the change in this PR — the guard is independent, and CI is green — but it means the concurrency pin in #664 should be read as reducing the flake, not fixing it. I have corrected that PR's description accordingly.

Verification

  • middleware: build OK · 6174 tests, 0 fail, 4 skipped · lint 0 errors · typecheck clean · typecheck:test ratchet unchanged at 406
  • test-file-durations.json is gitignored — it is a measurement of the machine that ran it, not a repo fact
  • Branch is on current main (3c86f1c8)

… size

#566 asks to split the two heaviest test files. Re-measured on 3c86f1c
first, and the premise no longer holds:

  slotTypecheckPipeline  18394 ms (issue) ->  2032 ms
  cliBackendDetector     16506 ms (issue) ->  3772 ms
  slowest file today: devplatform/dockerBackend.test.ts, 5163 ms

Neither named file is the slowest any more, and cliBackendDetector is 175
lines — splitting it into 4-6 files would be churn with nothing behind it.
So this ships what the issue is actually about: nothing detects a file
approaching the ceiling, which is why the margin went unwatched since #550
sized it.

`--test-timeout` kills the FILE, not the leaf, and blames whichever leaf was
running. The per-file total was not even observable: spec and tap flatten a
glob to suite names, so the filename is gone. `test:summary` is the one
event carrying both `file` and `duration_ms` — a second reporter on `npm
test` records it at no extra cost, and check-test-file-durations.mjs turns
it into a gate.

The gate is a FRACTION of the ceiling (warn 25%, fail 50%), not a committed
ms baseline. Durations vary by machine — this suite is ~36 s locally on 16
cores and ~172 s on a 4-vCPU runner — so an absolute number would be either
permanently red or permanently asleep, and would need re-committing as the
suite grows. That stale-baseline failure has bitten this repo before. The
ceiling is parsed out of the `test` script rather than duplicated, so
lowering --test-timeout tightens the guard automatically.

Today: 572 files, slowest 5163 ms, 23.2x headroom.

Mutation-checked rather than assumed — the guard exits 1 at 60000 ms (50%),
exits 0 at 59999 ms with a WARN, and also exits 1 on an empty file list or a
missing input, so it cannot pass while checking nothing.
…st-files

# Conflicts:
#	middleware/package.json
@Weegy
Weegy merged commit 1c26148 into main Aug 12, 2026
9 checks passed
@Weegy
Weegy deleted the fix/566-split-slow-test-files branch August 14, 2026 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--test-timeout is file-scoped, so a growing file goes red as a unit

1 participant