81 lines
3.0 KiB
TypeScript
81 lines
3.0 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { bunEnv, bunExe, tempDir } from "harness";
|
|
import { join } from "path";
|
|
|
|
// Regression test: when a bundler plugin throws a non-Error value whose string
|
|
// conversion also throws, `logger.Msg.fromJS` fails with a JS exception. Prior
|
|
// to the fix, `JSBundlerPlugin__addError` would return early without calling
|
|
// `onLoadAsync`/`onResolveAsync`, so the bundler's pending-item counter was
|
|
// never decremented and `Bun.build` would hang forever.
|
|
describe("Bun.build plugin throws value whose toString also throws", () => {
|
|
const fixture = (hook: "onLoad" | "onResolve") => /* js */ `
|
|
import { join } from "path";
|
|
const result = await Bun.build({
|
|
entrypoints: [join(import.meta.dir, "entry.ts")],
|
|
throw: false,
|
|
plugins: [
|
|
{
|
|
name: "bad-plugin",
|
|
setup(build) {
|
|
build.${hook}({ filter: ${hook === "onLoad" ? "/entry\\.ts$/" : "/^virtual:thing$/"} }, async () => {
|
|
// force the rejection to go through the promise path so addError() is hit
|
|
await Promise.resolve();
|
|
// Not an Error instance, so Msg.fromJS falls back to toBunString(),
|
|
// which invokes ToPrimitive -> Symbol.toPrimitive -> throws again.
|
|
throw {
|
|
[Symbol.toPrimitive]() {
|
|
throw new Error("nested throw during error conversion");
|
|
},
|
|
toString() {
|
|
throw new Error("nested throw during error conversion");
|
|
},
|
|
};
|
|
});
|
|
},
|
|
},
|
|
],
|
|
});
|
|
console.log(JSON.stringify({
|
|
success: result.success,
|
|
logs: result.logs.map(l => String(l.message ?? l)),
|
|
}));
|
|
`;
|
|
|
|
for (const hook of ["onLoad", "onResolve"] as const) {
|
|
test.concurrent(`${hook}: build completes instead of hanging`, async () => {
|
|
using dir = tempDir(`plugin-nested-throw-${hook}`, {
|
|
"entry.ts": hook === "onLoad" ? `console.log("hi");` : `import "virtual:thing"; console.log("hi");`,
|
|
"build.ts": fixture(hook),
|
|
});
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "run", join(String(dir), "build.ts")],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
// If the build hangs (the bug), kill it so the assertion below can report
|
|
// a useful diff instead of the test runner just timing out.
|
|
timeout: 10_000,
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
// If the subprocess was killed by the spawn timeout, the build hung.
|
|
expect({
|
|
stdout: stdout.trim(),
|
|
stderr: stderr.trim(),
|
|
exitCode,
|
|
signalCode: proc.signalCode ?? null,
|
|
}).toMatchObject({
|
|
exitCode: 0,
|
|
signalCode: null,
|
|
});
|
|
|
|
const parsed = JSON.parse(stdout.trim());
|
|
expect(parsed.success).toBe(false);
|
|
expect(parsed.logs.length).toBeGreaterThan(0);
|
|
});
|
|
}
|
|
});
|