diff --git a/packages/cli/test/vale-run.test.ts b/packages/cli/test/vale-run.test.ts index 39a28031..291b025e 100644 --- a/packages/cli/test/vale-run.test.ts +++ b/packages/cli/test/vale-run.test.ts @@ -258,15 +258,62 @@ withVale("runVale against the real binary", () => { }); it("terminates and reports a timeout rather than hanging", async () => { + // THE BUDGET AND THE INPUT ARE BOTH LOAD-BEARING. This asserted the winner + // of a race until taskless/cli#327: a 1ms budget against a one-line + // document, on the stated grounds that "1ms cannot survive process + // startup". That is not something the test controls. Vale runs in its OWN + // process and does not care whether our event loop is free, so under load + // the timer's callback is delayed while the child keeps going, and the run + // completes cleanly where the test demanded a timeout. + // + // NO ISSUE EVER FLAGGED THIS TEST. It was found while investigating + // taskless/cli#262, which reports a different flake entirely — two + // SUBPROCESS-SPAWNING tests in `error-envelope.test.ts` and + // `verify-test-commands.test.ts` — and does not name this one. #262 is + // where the search started, not what it found, and it remains open. + // + // Its sibling in `ValeRunOutcome.blocking` had the identical shape and was + // MEASURED failing that way, reporting `status: "ok"`, before it was given + // a real margin. This test survived only because its window was narrower, + // not because it was safe. + // + // TWO SEPARATE MEASUREMENT PASSES COUNTED THAT SIBLING, which is why the + // numbers here and in its own comment below differ and neither is wrong. + // The first, while #323 was open, saw it lose ONCE ACROSS FOUR concurrent + // full-suite runs. The second, counting a set of captured logs recovered + // later, saw TWICE ACROSS 13. Same test, same failure, different samples. + // + // The metric that matters is the ABSOLUTE margin (duration minus budget), + // not a ratio: what has to happen is the child finishing before a delayed + // timer callback runs. Measured on this fixture, warm: + // + // | fixture | bytes | duration | headroom over 100ms | + // | --------------- | ------ | -------- | ------------------- | + // | 19 (the old one)| 362 | ~46ms | 45ms — this flaked | + // | 8,000 | 152KB | ~1020ms | ~920ms | + // | 17,000 (sibling)| 323KB | ~4430ms | ~4330ms | + // + // 8,000 is chosen over the sibling's 17,000 deliberately: it is 20x the + // margin that actually flaked while costing a quarter of the suite time, + // and this test asserts the message rather than the blocking flag, which + // the sibling already covers with the larger fixture. + // + // `maxFileBytes` raises `VALE_MAX_FILE_BYTES` for THIS CALL ONLY — not a + // CLI flag, not a config surface, just a seam. Without it a 152KB document + // is excluded before Vale sees it (taskless/cli#321) and reports + // `status: "ok"` with a notice, never exercising the timeout at all. const cwd = makeProject( `${header}\n[*.md]\nno-simply.no-simply = YES\n`, { "no-simply": existenceRule("simply", "Avoid 'simply'") }, - { "doc.md": "Just simply do it.\n" } + { "doc.md": `${"Just simply do it. ".repeat(8000)}\n` } ); - // 1ms cannot survive process startup, so this exercises the kill path - // without needing a pathological corpus to provoke it. - const outcome = await runVale({ cwd, paths: ["doc.md"], timeoutMs: 1 }); + const outcome = await runVale({ + cwd, + paths: ["doc.md"], + timeoutMs: 100, + maxFileBytes: Number.POSITIVE_INFINITY, + }); expect(outcome.status).toBe("timeout"); if (outcome.status !== "timeout") return; expect(outcome.message).toContain("terminated");