Skip to content

bun test --isolate: opt-in global reuse fast path - #36871

Open
robobun wants to merge 11 commits into
mainfrom
farm/490b3121/isolate-fast-reset
Open

bun test --isolate: opt-in global reuse fast path#36871
robobun wants to merge 11 commits into
mainfrom
farm/490b3121/isolate-fast-reset

swap when JSMock__resetSpies throws

5c76a70
Select commit
Loading
Failed to load commit list.
Claude / Claude Code Review completed Aug 4, 2026 in 28m 5s

Code review found 4 important issues

Found 5 candidates, confirmed 4. See review comments for details.

Details

Severity Count
🔴 Important 4
🟡 Nit 0
🟣 Pre-existing 0
Severity File:Line Issue
🔴 Important src/jsc/bindings/ZigGlobalObject.cpp:901-906 process.on() listeners registered by --preload accumulate unboundedly across reuse cycles
🔴 Important src/jsc/bindings/ZigGlobalObject.cpp:903-909 jest.setSystemTime() without useFakeTimers() leaks overridenDateNow across the reuse path
🔴 Important src/jsc/VirtualMachine.rs:4705-4709 Stale baseline after reuse: preload-set object globals force swap on every second file
🔴 Important src/jsc/bindings/ZigGlobalObject.cpp:896-900 JSMock__resetSpies runs after own-property scrub, re-leaking spied globalThis properties

Annotations

Check failure on line 906 in src/jsc/bindings/ZigGlobalObject.cpp

See this annotation in the file changed.

@claude claude / Claude Code Review

process.on() listeners registered by --preload accumulate unboundedly across reuse cycles

`globalEventScope->removeAllEventListeners()` clears the DOM EventTarget behind `globalThis.addEventListener`, but nothing touches `processObject()->wrapped()` — the EventEmitter behind `process.on`. Since `preloadModuleKeys` now forces the preload chain to re-evaluate on every reuse, a preload that does `process.on('unhandledRejection', h)` appends one more listener to the surviving `Process` object per file: MaxListenersExceededWarning after ~10 files, N-fold handler execution, and N copies of

Check failure on line 909 in src/jsc/bindings/ZigGlobalObject.cpp

See this annotation in the file changed.

@claude claude / Claude Code Review

jest.setSystemTime() without useFakeTimers() leaks overridenDateNow across the reuse path

`jest.setSystemTime(ms)` without `useFakeTimers()` writes `globalObject->overridenDateNow` (JSMockFunction.cpp:1455) with no fake-timers gate, but `close_isolation_handles` only clears it via `fake_timers.reset_for_isolation` when `fake_timers.is_active()` is true — so on the reuse path the same global survives with the stale timestamp and the next file's `Date.now()` / `new Date()` returns the mocked value. This is the same native-per-global-field class as `m_cachedCwd` (which this PR does rese

Check failure on line 4709 in src/jsc/VirtualMachine.rs

See this annotation in the file changed.

@claude claude / Claude Code Review

Stale baseline after reuse: preload-set object globals force swap on every second file

The reuse early-return doesn't clear `baseline_captured`, so after a successful reuse the next file's `load_preloads` re-evaluates the (evicted) preload chain but `capture_test_isolation_baseline` at line 4460 is skipped. If the preload assigns an object to `globalThis` at top level (`globalThis.testUtils = {...}`, jsdom's `window`/`document`, etc.), the fresh identity no longer matches the baseline's `Strong<>` ref → `dirty=true` → swap, yielding reuse/swap/reuse/swap alternation (~50% reuse ra

Check failure on line 900 in src/jsc/bindings/ZigGlobalObject.cpp

See this annotation in the file changed.

@claude claude / Claude Code Review

JSMock__resetSpies runs after own-property scrub, re-leaking spied globalThis properties

`JSMock__resetSpies` runs *after* the own-property pristine check and `toDelete` scrub, so `clearSpy()`'s `putDirect(spyTarget, spyIdentifier, spyOriginal)` re-installs a globalThis property the scrub just deleted: a test that does `globalThis.foo = fn; jest.spyOn(globalThis, 'foo')` leaks `foo` into the next reused file. It also means `jest.spyOn(globalThis, 'setTimeout')` forces a full swap (the mock ≠ baseline at the pristine check) even though resetSpies would restore the exact baseline valu