-
Notifications
You must be signed in to change notification settings - Fork 5k
bun test: don't park the event loop after a test file's entry promise settles #36453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Jarred-Sumner
merged 6 commits into
main
from
farm/187bb123/test-runner-pending-timer-stall
Jul 31, 2026
+78
−0
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
07ca26c
bun test: don't park the event loop after a test file's entry promise…
robobun 7ad69c4
test: guard that the leak fixture armed its timer before measured files
robobun 22c255c
Shorten the pre-arm comment
robobun 1e9cecc
Make the pre-arm comment one line
robobun 087b7b0
ci: retrigger
robobun 0d7b612
test: surface child stderr when the gap count or exit code mismatches
robobun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import { expect, test } from "bun:test"; | ||
| import { bunEnv, bunExe, tempDir } from "harness"; | ||
|
|
||
| // https://github.com/oven-sh/bun/issues/36450 | ||
| // A pending ref'd timer left behind by one test file made every subsequent | ||
| // test file that loads a module stall until the next JSC housekeeping timer | ||
| // (~100ms): after the file's entry promise settled, the runner's final | ||
| // blocking tick parked in the poller because the ref'd timer kept the loop | ||
| // active. The stall sits between module evaluation and the first test | ||
| // callback, so that gap is what we measure, paired against a control run | ||
| // without the pending timer so machine speed cancels out. | ||
| test("pending ref'd timer does not stall subsequent test files", async () => { | ||
| const fileCount = 6; | ||
| const files: Record<string, string> = { | ||
| "leak.test.ts": ` | ||
| import { test, expect } from "bun:test"; | ||
| test("leaves one pending ref'd timer", () => { | ||
| setTimeout(() => {}, 300_000); | ||
| globalThis.__timerArmed36450 = true; | ||
| expect(1).toBe(1); | ||
| }); | ||
| `, | ||
| }; | ||
| for (let i = 1; i <= fileCount; i++) { | ||
| // The import must be used, otherwise it is elided and no module loads. | ||
| files[`mod${i}.ts`] = `export const v = ${i};`; | ||
| files[`plain${i}.test.ts`] = ` | ||
| import { test, expect } from "bun:test"; | ||
| import { v } from "./mod${i}"; | ||
| // All files share one process; prove the leak file ran first so the | ||
| // measured run really has a pending ref'd timer. | ||
| if (process.env.ISSUE_36450_EXPECT_TIMER === "1" && !globalThis.__timerArmed36450) { | ||
| throw new Error("expected leak.test.ts to arm its timer before this file"); | ||
| } | ||
| const loadedAt = performance.now(); | ||
| test("t${i}", () => { | ||
| console.log("GAP${i}:" + (performance.now() - loadedAt).toFixed(2)); | ||
| expect(v).toBe(${i}); | ||
| }); | ||
| `; | ||
|
robobun marked this conversation as resolved.
|
||
| } | ||
| using dir = tempDir("issue-36450", files); | ||
| const plainFiles = Array.from({ length: fileCount }, (_, i) => `plain${i + 1}.test.ts`); | ||
|
|
||
| async function medianGap(withLeak: boolean): Promise<number> { | ||
| await using proc = Bun.spawn({ | ||
| cmd: [bunExe(), "test", ...(withLeak ? ["leak.test.ts"] : []), ...plainFiles], | ||
| env: { ...bunEnv, ISSUE_36450_EXPECT_TIMER: withLeak ? "1" : "0" }, | ||
| cwd: String(dir), | ||
| stdout: "pipe", | ||
| stderr: "pipe", | ||
| }); | ||
| const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); | ||
|
|
||
| const gaps: number[] = []; | ||
| for (const match of (stdout + stderr).matchAll(/GAP\d+:([\d.]+)/g)) { | ||
| gaps.push(Number(match[1])); | ||
| } | ||
| // Include stderr so a child failure (e.g. the fixture guard) prints its | ||
| // own error instead of just a short gap count. | ||
| expect({ gapCount: gaps.length, exitCode, stderr }).toEqual({ | ||
| gapCount: fileCount, | ||
| exitCode: 0, | ||
| stderr: expect.any(String), | ||
| }); | ||
| return gaps.toSorted((a, b) => a - b)[Math.floor(gaps.length / 2)]; | ||
| } | ||
|
|
||
| const withoutTimer = await medianGap(false); | ||
| const withTimer = await medianGap(true); | ||
|
|
||
| // Unfixed, the pending timer pins every gap to the next JSC timer deadline | ||
| // (~15ms release, ~90ms debug on an idle machine); fixed, both runs behave | ||
| // identically. | ||
| expect(withTimer - withoutTimer).toBeLessThan(10); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.