Skip to content

test: stop jsx-production.test.ts from timing out on debug builds - #38102

Open
robobun wants to merge 1 commit into
mainfrom
farm/60374fe7/jsx-production-test-light-fixture
Open

test: stop jsx-production.test.ts from timing out on debug builds#38102
robobun wants to merge 1 commit into
mainfrom
farm/60374fe7/jsx-production-test-light-fixture

Conversation

@robobun

@robobun robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • bun bd test test/bundler/transpiler/jsx-production.test.ts fails on a debug build with this test timed out after 5000ms: 19 of the 32 cases on one run here, 4 on another, all 32 in the container that reported it. The file takes 37-43s.
  • A single case run alone takes 2.3-4.2s on a debug build, so as soon as the describe.concurrent block runs the 4x4x2 matrix together, cases cross the 5s default timeout more or less at random.
  • Cause: every case spawns test/bundler/transpiler/jsx-production.tsx (or the byte-identical jsx-dev/jsx-dev.tsx), which imports react-dom/server.browser, renders, runs Bun.build() on itself with react-dom bundled in, and imports the bundle. With the runtime transpiler cache disabled (bunEnv sets BUN_RUNTIME_TRANSPILER_CACHE_PATH=0) that is three passes over react-dom-sized source per case: the runtime transpile, the bundler's parse, and the runtime transpile of the resulting bundle.
  • The same cost shows on CI: test/expected-durations.json lists the file at 15.7s (default lane), 19.7s (asan) and 22.8s (windows). cli: stop --jsx-* flags from switching the automatic JSX runtime to production #36209 and Bind the JSX runtime and bun:wrap helpers with require() in CommonJS-wrapped modules #38012 both ran into these timeouts on debug builds and worked around them (a separate test file, and a note in the PR body, respectively).

Fix

  • The fixture now lives in a tempDir with a stub react whose jsx-runtime and jsx-dev-runtime return their own name. app.tsx imports child.tsx, prints which runtime the two files were compiled against, then runs Bun.build() on itself with process.env.NODE_ENV defined to CHILD_NODE_ENV and executes the bundle, which prints the same line for the bundled pass. jsx-production.tsx, jsx-production-entry.ts and jsx-dev/ are deleted. test/bundler/transpiler/tsconfig.json (added alongside them) is left alone: it applies to every other file in the directory, so removing it is a separate question.
  • Why this keeps the coverage: what Bun.build always assume "react-jsxdev" even when set "react-jsx" #3768 is about is which automatic runtime module the transpiler and the bundler pick given the environment's NODE_ENV, a define of process.env.NODE_ENV, and tsconfig jsx. The stub observes exactly that (which module's function actually ran, in both files), where the old fixture observed it indirectly: a code.includes("jsx_runtime.jsx") check on the bundle text, plus a react-dom render that produces the same HTML under either runtime. bundler_jsx.test.ts and jsx-tsconfig-react-jsx.test.ts already use stub runtimes for the same reason.
  • The bundle is still executed, so a bundle whose JSX calls and runtime import disagree (the failure mode in Bundler, jsxDEV in production breaking the build and the import #14978) still fails: each stub module only exports its own runtime's functions.
  • Same 32 cases and the same pins as before: a define of "production" has to bundle against jsx and "development" against jsxDEV whatever the parent process and tsconfig say, and the bundle has to run. Two additions: NODE_ENV=production in the environment is pinned to jsx for the bun run pass, and the imported file is now JSX too, so the bundler's separate entry-point and import paths for jsx.development (src/bundler/bundle_v2.rs) are both exercised. The combinations that fall back to tsconfig / the parent environment are deliberately left unpinned, as before; several open PRs are adjusting those semantics and this file only needs the fallback to produce a runtime that exists and to be applied consistently to both files.
  • Verified: bun bd test test/bundler/transpiler/jsx-production.test.ts: 32 pass, 6.6-9s for the file, slowest case 1.2s. USE_SYSTEM_BUN=1 bun test on the file: 32 pass in 0.67s. The old file on the same debug build: 19 of 32 timed out, 37.5s.
  • Mutation check: removing the process.env.NODE_ENV define from the fixture fails exactly the 8 cases whose define disagrees with the fallback, and passes the other 24, so the pins are live.
  • This is a test-only change; the before/after is the timeout above, not an assertion that flips on a source change.

Background

  • Automatic JSX runtime: with tsconfig "jsx": "react-jsx" or "react-jsxdev", JSX compiles to calls into a runtime module that Bun auto-imports: jsx/jsxs from <importSource>/jsx-runtime (production) or jsxDEV from <importSource>/jsx-dev-runtime (development). The import source defaults to react, which is why a stub package named react in the fixture's node_modules is all the resolver needs.
  • Dev/prod selection (Bun.build always assume "react-jsxdev" even when set "react-jsx" #3768, Fix loading react-jsxdev instead of react-jsx #17013): configure_defines in src/bundler/transpiler.rs reads a process.env.NODE_ENV define or the environment's NODE_ENV and sets production / force_node_env; otherwise the tsconfig jsx value decides. src/bundler/bundle_v2.rs applies that to each parse task, in one place for entry points and in another for resolved imports, which is why the fixture now has JSX in both an entry point and an imported file.
  • BUN_ENV takes precedence over NODE_ENV in Bun's env loader, so the test clears it; the matrix otherwise inherits bunEnv.

…stub runtime

Each of the 32 cases spawned a bun process that imported react-dom/server.browser,
rendered, ran Bun.build() on itself with react-dom bundled in, and imported the
bundle. On a debug+ASAN build that is 2-5s per spawn, so with describe.concurrent
a large share of the cases hit the default 5s timeout (19 of 32 on one run here),
and the file took 15-23s on CI lanes.

The fixture now lives in a tempDir with a stub `react` whose jsx-runtime and
jsx-dev-runtime return their own name. app.tsx imports child.tsx, prints which
runtime both files were compiled against, then Bun.build()s itself with
process.env.NODE_ENV defined to CHILD_NODE_ENV and executes the bundle, which
prints the same line for the bundled pass. The matrix and the assertions from
before are kept (a define of "production" has to bundle against jsx and
"development" against jsxDEV, and the bundle has to execute); NODE_ENV=production
at runtime is additionally pinned to jsx. The whole file runs in under a second
on a release build and about 8s on a debug build, with no case above 1.2s.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0067a5f5-f673-4b49-b323-47d93f238740

📥 Commits

Reviewing files that changed from the base of the PR and between b7a0431 and 398fe5b.

📒 Files selected for processing (5)
  • test/bundler/transpiler/jsx-dev/jsx-dev.tsx
  • test/bundler/transpiler/jsx-dev/tsconfig.json
  • test/bundler/transpiler/jsx-production-entry.ts
  • test/bundler/transpiler/jsx-production.test.ts
  • test/bundler/transpiler/jsx-production.tsx

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced, fixed, CI green.

  • Reproduced on a debug build at b7a0431 with bun bd test test/bundler/transpiler/jsx-production.test.ts: 19 of 32 cases timed out after 5000ms (4 of 32 on an earlier run), 37-43s for the file; each case spawns a fixture that transpiles and bundles react-dom.
  • With this branch the same command passes 32/32 in 6.6-9s on the debug build (slowest case 1.2s) and in 0.67s on a release build. Removing the process.env.NODE_ENV define from the fixture fails exactly the 8 cases it should, so the assertions are live.
  • Test-only change; no runtime code is touched.
  • CI: Buildkite build 94370 passed on every lane. The one red GitHub status (darwin-14-aarch64-test-bun, "Expired") is a first attempt that expired waiting for an agent; its retry passed, it just does not overwrite the status.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — test-only refactor that swaps the react-dom fixture for a stub runtime to fix debug-build timeouts, with coverage preserved.

What was reviewed:

  • Assertion parity with the old fixture: the child_node_envjsx/jsxDEV pins and bundle-import-must-run check are kept; the dropped react-dom render produced identical output under both runtimes so wasn't load-bearing.
  • Deleted fixtures (jsx-production.tsx, jsx-production-entry.ts, jsx-dev/) have no remaining references; the TSCONFIG_JSX env var the old test set was never read anywhere.
  • Harness conventions: tempDir, {...bunEnv, ...}, concurrent pipe drain via Promise.all, stderr asserted before exit code, toBeOneOf is an existing matcher.
  • Stub node_modules/react/jsx-{,dev-}runtime.js without a package.json matches the pattern in bundler_jsx.test.ts and bundler_edgecase.test.ts.
Extended reasoning...

Overview

This PR rewrites test/bundler/transpiler/jsx-production.test.ts to stop it timing out on debug builds. The old test spawned 32 concurrent subprocesses that each imported react-dom/server.browser, rendered, ran Bun.build() bundling react-dom, and imported the result — three passes over react-dom-sized source per case with the transpiler cache disabled. The new version writes a tempDir fixture with stub react/jsx-runtime and react/jsx-dev-runtime modules whose functions return their own name, so each case observes directly which runtime module the transpiler and bundler picked. The three fixture files and the jsx-dev/ directory are deleted; only the test file remains.

Security risks

None. Test-only change, no source code touched, no network access, hermetic tempDir fixture.

Level of scrutiny

Medium — the concern for a test rewrite is whether it silently weakens coverage. I compared the old and new assertions:

  • Old: code.includes("jsx_dev_runtime.jsxDEV") when CHILD_NODE_ENV === "development" and code.includes("jsx_runtime.jsx") when === "production", plus the bundle must import without throwing, plus react-dom must render <div>Hello World</div> twice.
  • New: bundlePass is pinned to ["jsx"] / ["jsxDEV"] for those same two child_node_env values, the bundle is still imported (and now its stdout is asserted), and the react-dom render is dropped. The render produced identical HTML under either runtime, so it distinguished nothing — the PR body calls this out and it checks out.

The new test adds two assertions the old one lacked: NODE_ENV=production in the parent env pins the bun run pass to jsx, and both the entry point and an imported .tsx file must be compiled against the same runtime (exercising the separate entry-point vs. resolved-import paths in bundle_v2.rs). It also asserts stderr === "" where the old test used stderr: "inherit". Net coverage is equal or slightly stronger.

Other factors

  • Grepped for references to the deleted fixture files and the TSCONFIG_JSX env var — none remain, so no dangling consumers.
  • toBeOneOf is an established matcher (used in 8 other test files).
  • The stub-runtime-in-node_modules/react/ pattern without a package.json is already used in test/bundler/bundler_jsx.test.ts and bundler_edgecase.test.ts.
  • Harness conventions are followed: tempDir with using, {...bunEnv, KEY: undefined} spread, Promise.all([stdout.text(), stderr.text(), exited]) to avoid pipe deadlock, stdout/stderr asserted before exit code.
  • The PR body documents both a bun bd test run (32 pass, 6.6-9s) and a mutation check (removing the define fails exactly the 8 expected cases), satisfying the "prove the test fails for the right reason" bar.
  • No prior human reviews or outstanding comments on the PR.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 6:45 AM PT - Aug 13th, 2026

@robobun, your commit 398fe5bb56ceec1b424c47999f9b86e035406ce5 passed in Build #94370! 🎉


🧪   To try this PR locally:

bunx bun-pr 38102

That installs a local version of the PR into your bun-38102 executable, so you can run:

bun-38102 --bun

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant