Repository navigation
Fix a crash when a module is removed while its import() is in flight #44281
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
Changes from all commits
5c8af2b
f8fd616
395cb3d
f440486
6bfdd9d
2e3695d
667f348
52f3fef
e57027b
17d21f9
2e25d2c
e311fe6
9636305
c787a5f
0c8a939
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -497,3 +497,46 @@ test.concurrent( | |
| expect(exitCode).toBe(0); | ||
| }, | ||
| ); | ||
|
|
||
| test.concurrent("mock.module() of a module whose import() is still loading its dependencies", async () => { | ||
| using dir = tempDir("mock-module-import-in-flight", { | ||
| "a.ts": `import "./dependency"; export const a = "real-a";`, | ||
| "dependency.ts": `export {};`, | ||
| "in-flight.test.ts": ` | ||
| import { expect, mock, test } from "bun:test"; | ||
|
|
||
| test("the import in flight gets the module it was loading, the next one gets the mock", async () => { | ||
| const dependencyRequested = Promise.withResolvers<void>(); | ||
| const dependencyMayLoad = Promise.withResolvers<void>(); | ||
| Bun.plugin({ | ||
| name: "hold the dependency's load open", | ||
| setup(build) { | ||
| build.onLoad({ filter: /dependency\\.ts$/ }, async () => { | ||
| dependencyRequested.resolve(); | ||
| await dependencyMayLoad.promise; | ||
| return { contents: "export {}", loader: "ts" }; | ||
| }); | ||
| }, | ||
| }); | ||
|
|
||
| const inFlight = import("./a"); | ||
| await dependencyRequested.promise; | ||
| mock.module("./a", () => ({ a: "mocked-a" })); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟣 pre-existing, not blocking: pre-existing: a test that calls mock.module() while the target's own fetch (its onLoad) is still pending keeps getting the real module from the next import(), silently, on base and after this PR. mock.module at BunPlugin.cpp:522 finds no registry entry because the C++ loader registers a key only after its fetch settles (ModuleLoader.cpp:494-507), so nothing is removed; the pending fetch then registers the real module and import() at ZigGlobalObject.cpp:3686 reuses it. Same for build.module() (BunPlugin.cpp:162) and a --hot reload during the fetch, which re-registers the pre-reload module. Fix: cover this sibling of the in-flight case, e.g. register the entry before fetch (the FIXME) or note it as excluded in the PR. Why this was flaggedTrigger: a plugin onLoad matching a.ts itself returns a pending promise; test code runs Verification: pre-existing: triggers when mock.module() (or build.module()) targets a module whose own fetch is still pending; the base fails the same way. BunPlugin.cpp:522-524 finds no registry entry, so staleESMEntry stays false and removeEntry never runs. ModuleLoader.cpp:494-507: the loader does not create a registry entry until after the fetch promise resolves. Consequence: wrong behavior, silent. |
||
| dependencyMayLoad.resolve(); | ||
|
|
||
| expect((await inFlight).a).toBe("real-a"); | ||
| expect((await import("./a")).a).toBe("mocked-a"); | ||
| }); | ||
| `, | ||
| }); | ||
| await using proc = Bun.spawn({ | ||
| cmd: [bunExe(), "test", "./in-flight.test.ts"], | ||
| cwd: String(dir), | ||
| env: bunEnv, | ||
| stdout: "pipe", | ||
| stderr: "pipe", | ||
| }); | ||
| const [, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); | ||
| expect(stderr).toContain(" 1 pass"); | ||
| expect(exitCode).toBe(0); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.