Skip to content

fix(core): share imported module state and keep context-bound APIs live across files under isolate: false - #1376

Merged
fi3ework merged 7 commits into
mainfrom
fix-no-isolate-module-sharing
Jun 24, 2026
Merged

fix(core): share imported module state and keep context-bound APIs live across files under isolate: false#1376
fi3ework merged 7 commits into
mainfrom
fix-no-isolate-module-sharing

Conversation

@fi3ework

@fi3ework fi3ework commented Jun 4, 2026

Copy link
Copy Markdown
Member

Summary

Under isolate: false, one worker process runs many test files: @rstest/core is reset per file (its module cache is evicted and re-imported) while user modules persist — a helper imported by several files is evaluated only once per worker. Two problems followed from that asymmetry, both fixed here.

1. Imported module state was not shared across files (#1373)

The worker teardown cleared the entire module cache after every file, evicting the shared per-worker runtime chunk that owns the only __webpack_module_cache__. The next file re-instantiated that runtime, so every bundled module factory ran again and module-scope state (singletons, caches, lazily-initialized resources) did not survive across files — defeating the main reason to opt into non-isolated mode.

Teardown invalidation is now selective:

  • Keep the shared runtime chunk so __webpack_module_cache__ — and the state of every already-evaluated non-entry module — persists across files. Only the test-entry and setup modules are evicted so their bodies re-run per file.
  • Accumulate per-entry asset maps into one persistent per-worker map, so a later file's chunks stay resolvable by the kept runtime chunk's import.meta hooks (wasm / dynamic-import resolution). The map is reset only on a full cache clear.
  • Normalize setup paths with pathe before matching, so setup files reliably re-run per file on Windows / mixed separators (previously a raw-path collection clobber left the targeted setup-reset list empty).

2. Context-bound APIs went stale when value-copied into a shared helper

Because user modules persist, any context-bound @rstest/core value captured at module top-level in a persisted helper — export const test = base.extend({}), a re-exported onTestFinished, { ...rstest }, or a snapshotted expect.poll — froze to the first file's torn-down context and silently misbehaved from the second file on.

Fixed with a single live-binding contract: each context-bound member is a stable forwarder, built once, that re-resolves the running file's state from a per-file global slot at call time — never closing over a per-file instance. The collection surface (runtimeAPI) and the full runner surface (runnerAPI) are built once; createRuntimeAPI only constructs the file's runtime and publishes its slot. expect additionally self-delegates to the live GLOBAL_EXPECT to rescue an already-copied expect.poll/.soft. The contract — the four slots, the shared value-copy residual, and why expect is the one self-delegating member — is documented in one place (runtime/api/index.ts).

3. Shared module state went stale across watch rebuilds

Sharing user modules means keeping the runtime chunk (which owns the only __webpack_module_cache__) across files. In watch mode the worker pool is reused across rebuilds, so that kept cache also survived rebuild boundaries — under isolate: false a changed shared module was re-run with the previous build's exports until the process restarted.

A per-compile buildId is now threaded to the worker. When it changes (every watch rebuild), the worker fully flushes its module cache before loading the new build, so each rebuild re-evaluates shared modules. Intra-build sharing across files is unchanged.

Behavior

With isolate: false, an imported module now evaluates once per worker and is shared across the files that run in it, and context-bound APIs always act on the running file; the test entry, setup files, and @rstest/core still reset per file. Applies to both ESM and CommonJS output.

A new e2e surface guard drives the whole context-bound API surface (test.extend, hooks, describe, expect.poll, rstest.fn/clearMocks) through a persisted shared helper from a non-first file; a watch regression test asserts that an edited shared module is re-evaluated on rerun; and the isolate docs note that module-scope code runs once per worker (use setup files for per-file setup).

Related Links

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ad9c97bfa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/runInPool.ts
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Rsdoctor Bundle Diff Analysis

Found 13 projects in monorepo, 2 projects with changes.

📊 Quick Summary
Project Total Size Change
adapter-rsbuild 4.1 KB 0
adapter-rslib 24.7 KB 0
adapter-rspack 8.0 KB 0
browser 2.0 MB 0
browser-react 3.7 KB 0
browser-ui 809.6 KB 0
coverage-istanbul 16.0 KB 0
coverage-v8 17.5 KB 0
core/browser 980.2 KB +2.1 KB (0.2%)
core/loaders 869.0 B 0
core/main 1.7 MB +3.5 KB (0.2%)
vscode/extension 27.0 MB 0
vscode/worker 14.5 KB 0
📋 Detailed Reports (Click to expand)

📁 core/browser

Path: packages/core/.rsdoctor/browser/rsdoctor-data.json

📌 Baseline Commit: 6360bcba54 | PR: #1398

Metric Current Baseline Change
📊 Total Size 980.2 KB 978.1 KB +2.1 KB (0.2%)
📄 JavaScript 972.1 KB 970.0 KB +2.1 KB (0.2%)
🎨 CSS 0 B 0 B 0
🌐 HTML 0 B 0 B 0
📁 Other Assets 8.1 KB 8.1 KB 0

📦 Download Diff Report: core/browser Bundle Diff

📁 core/main

Path: packages/core/.rsdoctor/main/rsdoctor-data.json

📌 Baseline Commit: 6360bcba54 | PR: #1398

Metric Current Baseline Change
📊 Total Size 1.7 MB 1.7 MB +3.5 KB (0.2%)
📄 JavaScript 1.6 MB 1.6 MB +3.5 KB (0.2%)
🎨 CSS 0 B 0 B 0
🌐 HTML 0 B 0 B 0
📁 Other Assets 81.6 KB 81.6 KB 0

📦 Download Diff Report: core/main Bundle Diff

Generated by Rsdoctor GitHub Action

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e703cf8d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/loadEsModule.ts
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

pkg.pr.new preview 5e703cf · workflow run

  • pnpm add https://pkg.pr.new/@rstest/adapter-rsbuild@5e703cf
  • pnpm add https://pkg.pr.new/@rstest/adapter-rslib@5e703cf
  • pnpm add https://pkg.pr.new/@rstest/adapter-rspack@5e703cf
  • pnpm add https://pkg.pr.new/@rstest/browser@5e703cf
  • pnpm add https://pkg.pr.new/@rstest/browser-react@5e703cf
  • pnpm add https://pkg.pr.new/@rstest/core@5e703cf
  • pnpm add https://pkg.pr.new/@rstest/coverage-istanbul@5e703cf
  • pnpm add https://pkg.pr.new/@rstest/coverage-v8@5e703cf

fi3ework added a commit that referenced this pull request Jun 4, 2026
Under `isolate: false` the shared-module fix persists user modules across
files, but `test.extend(...)` captured the per-file runner eagerly: a fixture
module doing `export const test = base.extend(...)` evaluated once, so from the
second file on every registration hit the first file's torn-down runner and
threw "Test '<name>' cannot run".

Make the runner API intrinsically late-bound, mirroring the existing
`RSTEST_API` proxy: publish the current file's runner as `globalThis.RSTEST_RUNTIME`
(reassigned per file) and resolve it at every leaf registration call instead of
closing over `runtimeInstance`. `getLocation` moves onto the runner so a
persisted extended test also computes locations against the current file.

Closes #1376.
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

pkg.pr.new preview 101313b · workflow run

  • pnpm add https://pkg.pr.new/@rstest/adapter-rsbuild@101313b
  • pnpm add https://pkg.pr.new/@rstest/adapter-rslib@101313b
  • pnpm add https://pkg.pr.new/@rstest/adapter-rspack@101313b
  • pnpm add https://pkg.pr.new/@rstest/browser@101313b
  • pnpm add https://pkg.pr.new/@rstest/browser-react@101313b
  • pnpm add https://pkg.pr.new/@rstest/core@101313b
  • pnpm add https://pkg.pr.new/@rstest/coverage-istanbul@101313b
  • pnpm add https://pkg.pr.new/@rstest/coverage-v8@101313b

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 101313be9f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/runner/runtime.ts
fi3ework added a commit that referenced this pull request Jun 4, 2026
A follow-up to the test.extend() fix: under `isolate: false` a context-bound
`@rstest/core` API value-copied into a module shared across files goes stale.
A shared helper doing `Object.assign(impl, expect)` (or `const { poll } = expect`)
captures the first file's concrete `expect`, whose `expect.poll` is bound to that
file's `getCurrentTest`; from the second file on it threw "expect.poll() must be
called inside a test".

Make the per-file `expect` self-delegate: when invoked from a stale cross-file
reference it forwards to the current file's live expect (`globalThis[GLOBAL_EXPECT]`,
reassigned per file), so test attribution and assertion state track the running
file. The per-test local expect leaves `isFileExpect: false` so it never delegates
and keeps `test.concurrent` assertion isolation. For the current file's own expect
`live === expect`, so this is a no-op fast path.

Refs #1376.
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

pkg.pr.new preview 6a4cc28 · workflow run

  • pnpm add https://pkg.pr.new/@rstest/adapter-rsbuild@6a4cc28
  • pnpm add https://pkg.pr.new/@rstest/adapter-rslib@6a4cc28
  • pnpm add https://pkg.pr.new/@rstest/adapter-rspack@6a4cc28
  • pnpm add https://pkg.pr.new/@rstest/browser@6a4cc28
  • pnpm add https://pkg.pr.new/@rstest/browser-react@6a4cc28
  • pnpm add https://pkg.pr.new/@rstest/core@6a4cc28
  • pnpm add https://pkg.pr.new/@rstest/coverage-istanbul@6a4cc28
  • pnpm add https://pkg.pr.new/@rstest/coverage-v8@6a4cc28

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a4cc28506

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/runner/runtime.ts Outdated
Comment thread packages/core/src/runtime/api/expect.ts Outdated
@fi3ework
fi3ework marked this pull request as draft June 4, 2026 16:12
fi3ework added a commit that referenced this pull request Jun 4, 2026
…solate: false

Continues the #1376 family: under `isolate: false` a context-bound
`@rstest/core` API value-copied into a module shared across files goes stale.
The file-level hooks (`afterAll`/`beforeAll`/`afterEach`/`beforeEach`) were left
bound to the first file's runner, so a shared helper re-exporting a hook (e.g.
`export const beforeEach = beforeEach`) silently registered into the first
file's torn-down collector from the second file on. `onTestFinished`/
`onTestFailed` had the same staleness against the per-file test runner and threw
"can only be called inside a test".

Route the hooks through `currentRuntime()` (the per-file `RSTEST_RUNTIME`, like
`it`/`describe`), and `onTestFinished`/`onTestFailed` through a new per-file
`globalThis.RSTEST_RUNNER` resolved at call time. The remaining `rstest`/`rs`
utilities object is the one known, non-idiomatic residual (re-exporting the whole
namespace), deferred until a real repro surfaces.

Refs #1376.
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

pkg.pr.new preview 41191b8 · workflow run

  • pnpm add https://pkg.pr.new/@rstest/adapter-rsbuild@41191b8
  • pnpm add https://pkg.pr.new/@rstest/adapter-rslib@41191b8
  • pnpm add https://pkg.pr.new/@rstest/adapter-rspack@41191b8
  • pnpm add https://pkg.pr.new/@rstest/browser@41191b8
  • pnpm add https://pkg.pr.new/@rstest/browser-react@41191b8
  • pnpm add https://pkg.pr.new/@rstest/core@41191b8
  • pnpm add https://pkg.pr.new/@rstest/coverage-istanbul@41191b8
  • pnpm add https://pkg.pr.new/@rstest/coverage-v8@41191b8

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 4, 2026

Copy link
Copy Markdown

Deploying rstest with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3589f34
Status: ✅  Deploy successful!
Preview URL: https://100cdfd1.rstest.pages.dev
Branch Preview URL: https://fix-no-isolate-module-sharin.rstest.pages.dev

View logs

@fi3ework

fi3ework commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 429ac70f7f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/runInPool.ts
fi3ework added a commit that referenced this pull request Jun 5, 2026
Under `isolate: false` a single worker runs many test files: `@rstest/core` is
reset per file (its module cache is evicted and re-imported, #1373) while user
modules persist — a helper imported by several files is evaluated only once.
Two problems followed from that asymmetry:

1. Context-bound `@rstest/core` APIs value-copied into a persisted helper (e.g.
   `export const test = base.extend({})`, a re-exported hook, `onTestFinished`,
   `{ ...rstest }`, or a snapshotted `expect.poll`) froze to the FIRST file's
   torn-down context and silently misbehaved from the second file on.

2. Setup files were matched against the module cache by raw path, so on Windows
   / mixed separators the cache-control plugin failed to re-run them per file.

Fix the binding problem with a single live-binding contract: each context-bound
member is a STABLE forwarder, built once at module load, that re-resolves the
running file's state from a per-file global slot at call time — never closing
over a per-file instance. The collection surface (`runtimeAPI`) and the full
runner surface (`runnerAPI`) are built once; `createRuntimeAPI` only constructs
the file's runtime and publishes its slot. `expect` additionally self-delegates
to the live `GLOBAL_EXPECT` to rescue an already-copied `expect.poll`/`.soft`.
The contract (the four slots, the shared value-copy residual, and why `expect`
is the one self-delegating member) is documented in one place,
`runtime/api/index.ts`.

Fix the setup-file problem by normalizing paths with `pathe` before matching.

Add an e2e surface guard that drives the whole context-bound API surface
(`test.extend`, hooks, `describe`, `expect.poll`, `rstest.fn`/`clearMocks`)
through a persisted shared helper from a non-first file, and document that
module-scope code runs once per worker under `isolate: false`.

Closes #1376
@fi3ework
fi3ework force-pushed the fix-no-isolate-module-sharing branch from 429ac70 to a8d3b0f Compare June 5, 2026 03:46
@fi3ework fi3ework changed the title fix(core): share imported module state across files under isolate: false fix(core): share imported module state and keep context-bound APIs live across files under isolate: false Jun 5, 2026
@fi3ework
fi3ework force-pushed the fix-no-isolate-module-sharing branch 8 times, most recently from 4f70360 to 88fac87 Compare June 10, 2026 04:10
@fi3ework
fi3ework marked this pull request as ready for review June 11, 2026 15:17
@fi3ework
fi3ework marked this pull request as draft June 11, 2026 15:17

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88fac87241

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/api/utilities.ts Outdated
@fi3ework
fi3ework force-pushed the fix-no-isolate-module-sharing branch 2 times, most recently from f51906e to 47db30f Compare June 22, 2026 10:47
Under `isolate: false` a single worker runs many test files: `@rstest/core` is
reset per file (its module cache is evicted and re-imported, #1373) while user
modules persist — a helper imported by several files is evaluated only once.
Two problems followed from that asymmetry:

1. Context-bound `@rstest/core` APIs value-copied into a persisted helper (e.g.
   `export const test = base.extend({})`, a re-exported hook, `onTestFinished`,
   `{ ...rstest }`, or a snapshotted `expect.poll`) froze to the FIRST file's
   torn-down context and silently misbehaved from the second file on.

2. Setup files were matched against the module cache by raw path, so on Windows
   / mixed separators the cache-control plugin failed to re-run them per file.

Fix the binding problem with a single live-binding contract: the whole injected
API surface (`runtimeAPI`, `runnerAPI`, `expect`, `rstest`) is built ONCE per
worker with a stable identity, and every context-bound member resolves the
running file's `FileContext` at call time — one module-level binding holding
the file's worker state, collection registrar, and test runner, republished as
one unit by `createRunner` — never closing over a per-file instance. Per-file
state is RESET, not rebuilt: `expect` clears its assertion bookkeeping and
re-establishes the live `testPath` getter, `rstest` drops its stub/timer/mock
bookkeeping and rewinds the shared `invocationCallOrder` counter. Because the
file-level `expect` is a build-once singleton, a
value-copied `expect.poll`/`.soft` can never go stale, so no cross-file
delegation is needed; the per-test local expect intentionally stays a pinned
per-test instance to keep `test.concurrent` isolation. Construction and
publication are separated (`createRuntimeAPI` is a pure factory; only
`createRunner` publishes the context), and globalThis carries only true
cross-boundary contracts (`RSTEST_API` for the user bundle, `GLOBAL_EXPECT`
for the assertion ecosystem). The contract is documented in one place,
`runtime/api/index.ts`.

Fix the setup-file problem by normalizing paths with `pathe` before matching.

Sharing user modules means keeping the runtime chunk (which owns the only
`__webpack_module_cache__`) across files. In watch mode the pool reuses the
worker across rebuilds, so that kept cache also survived rebuild boundaries and
a changed shared module was served stale from the previous build. Thread a
per-compile `buildId` to the worker and fully flush its module cache when the id
changes, so every rebuild re-evaluates shared modules while in-build sharing is
preserved.

Keeping the runtime chunk must also survive worker reuse across projects: the
pool has no environment affinity, so under `isolate: false` a reused worker can
serve project A, then B, then A again. Retaining only the latest runtime chunk
id let one project's teardown evict another's `__webpack_module_cache__` and
re-evaluate its shared modules on the next file. Accumulate every project's
runtime chunk into a kept set instead, reset only on a full clear (the
watch-rebuild boundary), so cross-file sharing holds per project.

Add an e2e surface guard that drives the whole context-bound API surface
(`test.extend`, hooks, `describe`, `expect.poll`, `rstest.fn`/`clearMocks`)
through a persisted shared helper from a non-first file, a watch regression test
asserting an edited shared module is re-evaluated on rerun, a unit test that a
reused worker keeps every project's runtime chunk, and document that
module-scope code runs once per worker under `isolate: false`.

Closes #1373
@fi3ework
fi3ework force-pushed the fix-no-isolate-module-sharing branch from 47db30f to 3589f34 Compare June 22, 2026 11:03
@fi3ework

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3589f34737

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/runInPool.ts
Comment thread packages/core/src/runtime/worker/loadModule.ts
Comment thread packages/core/src/runtime/api/utilities.ts Outdated
Under `isolate: false` a reused worker keeps several projects' runtime
chunks alive at once (keptRuntimeChunks). The cache-control runtime module
wrote a single `global.__rstest_clean_core_cache__` slot and a single
`global.setupIds`, both last-writer-wins across chunks, so once a sibling
project's chunk was evaluated the worker could clean the wrong project's
`__webpack_module_cache__` before a file — that project's setup module would
then be served from cache and stop re-running per file.

Make the cleanup order-independent: each chunk registers a self-scoped
cleaner into a `global.__rstest_cache_cleaners__` Set and tracks its setup
ids in a chunk-local `__rstest_setup_ids__` (registered through the chunk's
own `__webpack_require__`). The worker invokes every registered cleaner per
file; each only touches its own cache, so over-calling is a harmless no-op.
The full flush clears the registry so chunks re-register on rebuild.
@fi3ework
fi3ework marked this pull request as ready for review June 23, 2026 07:21

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88b0df9f3f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/core/plugins/moduleCacheControl.ts
fi3ework added 2 commits June 23, 2026 16:46
A reused worker under `isolate: false` can serve both ESM- and CJS-output
projects, each backed by a different loader with its own module cache. On a
watch rebuild (bumped `buildId`) the shared `lastBuildId` gate only flushed the
loader picked by the first task to see the new build, so the other loader kept
serving its previous-build runtime cache.

Centralize the full flush in `flushAllLoaderCaches` (interop.ts) and clear both
loaders on the rebuild boundary.

Addresses #1376 (comment)
…e: false

The mock registry is a per-worker singleton reset per file by `resetForFile`.
Under `isolate: false` a mock defined in a module shared across files persists
(the module is evaluated once), but clearing the registry per file dropped it
and it was never re-registered — so `clearMocks`/`resetMocks`/`restoreMocks`
stopped resetting it across the file boundary and its call history leaked into
later files.

Hold the registry as weak references and stop clearing it per file: a mock kept
alive by a shared module stays tracked, while a file-local mock falls out once
its evicted module is garbage-collected. Iterate via `forEachMock`, pruning
dead refs as it goes.

Adds an e2e regression to the `sharing` fixture (sharedMock.ts +
mockShareFirst/Second.test.ts) and migrates the spy unit test to the new
`forEachMock` interface.

Addresses #1376 (comment)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ddce513a3e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/api/utilities.ts
fi3ework added 2 commits June 23, 2026 16:59
`pool.minWorkers` was removed from the config in #1451. These fixtures (added in
this PR) still set it, so the type-check fails once the PR is merged into a main
that no longer declares the field. `maxWorkers: 1` already forces a single
worker, so the field is redundant; dropping it keeps the single-worker
determinism and unblocks CI lint.
The mock registry is a per-worker singleton. With the registry kept across files
(so shared-module mocks stay tracked), a reused worker that serves multiple
projects under `isolate: false` would let one project's configured
`clearMocks`/`resetMocks`/`restoreMocks` run over another project's kept mocks —
the pool has no project affinity and the shared `buildId` means no full flush
between projects.

Key the registry by the running file's project (`workerState.project`): a mock
registers into its project's bucket and `forEachMock` only iterates the current
project's. No module is shared across projects, so every mock belongs to exactly
one, mirroring the per-chunk cache cleaners.

Addresses #1376 (comment)
@github-actions

Copy link
Copy Markdown

pkg.pr.new preview 220a248 · workflow run

  • pnpm add https://pkg.pr.new/@rstest/adapter-rsbuild@220a248
  • pnpm add https://pkg.pr.new/@rstest/adapter-rslib@220a248
  • pnpm add https://pkg.pr.new/@rstest/adapter-rspack@220a248
  • pnpm add https://pkg.pr.new/@rstest/browser@220a248
  • pnpm add https://pkg.pr.new/@rstest/browser-react@220a248
  • pnpm add https://pkg.pr.new/@rstest/core@220a248
  • pnpm add https://pkg.pr.new/@rstest/coverage-istanbul@220a248
  • pnpm add https://pkg.pr.new/@rstest/coverage-v8@220a248

Use `map.get(key) ?? fresh` then `set`, mirroring the env/global stub stacks in
utilities.ts, instead of a bespoke `if (!set)` block.
@fi3ework
fi3ework requested a review from 9aoy June 23, 2026 12:33
@9aoy

9aoy commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

👍

@fi3ework
fi3ework merged commit dfdd527 into main Jun 24, 2026
12 checks passed
@fi3ework
fi3ework deleted the fix-no-isolate-module-sharing branch June 24, 2026 03:40
fi3ework added a commit that referenced this pull request Jul 9, 2026
Codex review of #1523: with only surfaceA/surfaceB, the shared afterAll of
whichever file runs second (the non-first, #1376-regression-prone position)
is never observed — the counter is only checked by the *next* file's
beforeAll, and there is no file after the last one.

Add a third peer (surfaceC) so the second-running file's afterAll increment
is asserted by the third-running file's beforeAll invariant, closing the gap
without reintroducing an order assumption. Verified in both alphabetical and
reverse order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Imported module state is not shared across test files under isolate: false

2 participants