bun test --isolate: opt-in global reuse fast path - #36871
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
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
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
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
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