Merge pull request #2181 from modelcontextprotocol/v2/fix/2180-render-smoke-startup-independent

test(scripts): anchor the render-smoke overlap paint to the parent's clock
This commit is contained in:
Cliff Hall 2026-08-28 00:21:13 -04:00 • committed by GitHub
commit e2de318698
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -86,20 +86,71 @@ test("passes when the child paints the marker and keeps running", async () => {
// visibly contains the marker. A slow-but-fine TUI would fail, intermittently,
// with a diagnostic pointing at the wrong thing entirely.
test("a paint landing just under the deadline is not failed by the render timer", async () => {
const paintAt = 250;
// The two clocks start at different moments: the child's `setTimeout` runs
// from its own first JS tick, while `renderTimer` is armed in the parent at
// spawn. A delay written as a plain `setTimeout(paint, N)` therefore lands at
// `N + startup`, so the deadline has to carry a startup allowance — and the
// case fails, as "did not render", whenever the machine is loaded enough to
// exceed it. It failed three `local:gate` runs in a row that way while
// passing every time standalone (#2177), and widening the allowance only
// moved the threshold (#2180).
//
// So the paint is anchored to the PARENT's clock instead: the child is told
// the wall-clock instant to paint at, and sleeps for whatever is left of it
// when it gets there. Startup is then *absorbed* by the sleep rather than
// added to it, and the paint lands at ~`PAINT_AT` measured from spawn no
// matter how slow the boot was.
const spawnedAt = Date.now();
const PAINT_AT = 300;
const timeoutMs = 600;
const surviveMs = 900;
const r = await runRenderSmoke({
...opts,
...stub(
`setTimeout(() => { console.log(${JSON.stringify(MARKER)}); ` +
`setInterval(() => {}, 1000); }, ${paintAt});`,
`const at = ${spawnedAt + PAINT_AT};` +
`setTimeout(() => { console.log(${JSON.stringify(MARKER)}); ` +
`setInterval(() => {}, 1000); }, Math.max(0, at - Date.now()));`,
),
// Deliberately tight: the render deadline expires while the survival window
// is still open, which is the whole shape of the bug.
timeoutMs: paintAt + 150,
surviveMs: 400,
// The shape being reproduced is an overlap, and only an overlap:
//
// actual paint < timeoutMs < actual paint + surviveMs
//
// i.e. the render deadline expires while the survival window is still
// open. `done` is first-caller-wins, so an armed render timer firing in
// that gap settles the run as "did not render <marker>" while quoting an
// output tail that visibly contains the marker.
//
// `timeoutMs < surviveMs` is what makes the right-hand side hold for ANY
// paint time, including 0 — so a boot slow enough to swallow the anchor
// still reproduces the overlap rather than degrading into a different
// case. The only startup assumption left is that boot finishes before the
// deadline at all, and 600ms is an order of magnitude over the 46-59ms
// measured here idle.
//
// Do not tighten these to make the test faster, and do not reorder them so
// that `surviveMs <= timeoutMs`: the overlap is the point.
timeoutMs,
surviveMs,
});
assert.equal(r.code, 0, `expected pass, got: ${r.message}`);
assert.doesNotMatch(r.message, /did not render/);
// Passing is not on its own evidence that the overlap was reproduced — a
// paint late enough to land past `timeoutMs` fails, but one landing so early
// that the deadline falls outside the survival window would pass while
// testing nothing. Read the paint time back out of the verdict and assert
// the shape actually held, so the case cannot go vacuous.
const paintedAt = Number(/ at (\d+)ms/.exec(r.message)?.[1]);
assert.ok(
Number.isFinite(paintedAt),
`no first-paint time in verdict: ${r.message}`,
);
assert.ok(
paintedAt < timeoutMs && timeoutMs < paintedAt + surviveMs,
`deadline did not fall inside the survival window: painted at ${paintedAt}ms, ` +
`timeoutMs ${timeoutMs}, surviveMs ${surviveMs}`,
);
});
test("fails when the child exits before painting", async () => {