diff --git a/test/regression/issue/26660.test.ts b/test/regression/issue/26660.test.ts index c067b473b1..349ce95bd5 100644 --- a/test/regression/issue/26660.test.ts +++ b/test/regression/issue/26660.test.ts @@ -1,31 +1,15 @@ import { expect, test } from "bun:test"; -import { bunEnv, bunExe, tempDir } from "harness"; +import { bunEnv, bunExe } from "harness"; // Test for issue #26660: Shell interpreter crash when spawning many concurrent shell commands // The bug was that IOWriter could have pending chunks referencing freed memory after // builtin/cmd cleanup, causing use-after-free crashes. test("many concurrent shell commands should not crash", async () => { - // Create a test script that spawns many shell commands concurrently - // This is similar to what OpenCode does which triggered the original crash - using dir = tempDir("issue-26660", { - "test.ts": ` - const promises: Promise[] = []; - - // Spawn 60 shell commands concurrently (similar to the crash report) - for (let i = 0; i < 60; i++) { - promises.push( - Bun.$\`echo "Hello from shell \${i}"\`.text().then(() => {}) - ); - } - - await Promise.all(promises); - console.log("All shell commands completed successfully"); - `, - }); + // Spawn many shell commands concurrently (similar to what triggered the crash) + const script = `const promises = []; for (let i = 0; i < 60; i++) { promises.push(Bun.$\`echo "Hello from shell \${i}"\`.text().then(() => {})); } await Promise.all(promises); console.log("All shell commands completed successfully");`; await using proc = Bun.spawn({ - cmd: [bunExe(), "run", "test.ts"], - cwd: String(dir), + cmd: [bunExe(), "-e", script], env: bunEnv, stderr: "pipe", stdout: "pipe", @@ -33,34 +17,17 @@ test("many concurrent shell commands should not crash", async () => { const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); - expect(stderr).not.toContain("Segmentation fault"); - expect(stderr).not.toContain("panic"); expect(stdout).toContain("All shell commands completed successfully"); + expect(stderr).toBe(""); expect(exitCode).toBe(0); }); test("concurrent shell commands with output should not crash", async () => { // Test with commands that produce output, which exercises IOWriter more - using dir = tempDir("issue-26660-output", { - "test.ts": ` - const promises: Promise[] = []; - - // Spawn many echo commands that all write to IOWriter - for (let i = 0; i < 40; i++) { - promises.push( - Bun.$\`echo "Line \${i}: This is a test with some longer output to exercise the IOWriter buffer"\`.text() - ); - } - - const results = await Promise.all(promises); - console.log("Collected", results.length, "results"); - console.log("All outputs received correctly"); - `, - }); + const script = `const promises = []; for (let i = 0; i < 40; i++) { promises.push(Bun.$\`echo "Line \${i}: test output"\`.text()); } const results = await Promise.all(promises); console.log("Collected", results.length, "results"); console.log("All outputs received correctly");`; await using proc = Bun.spawn({ - cmd: [bunExe(), "run", "test.ts"], - cwd: String(dir), + cmd: [bunExe(), "-e", script], env: bunEnv, stderr: "pipe", stdout: "pipe", @@ -68,33 +35,17 @@ test("concurrent shell commands with output should not crash", async () => { const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); - expect(stderr).not.toContain("Segmentation fault"); - expect(stderr).not.toContain("panic"); expect(stdout).toContain("All outputs received correctly"); + expect(stderr).toBe(""); expect(exitCode).toBe(0); }); test("shell commands with mixed builtins should not crash", async () => { // Test various builtins that use IOWriter - using dir = tempDir("issue-26660-builtins", { - "test.ts": ` - const promises: Promise[] = []; - - // Spawn various builtin commands concurrently - for (let i = 0; i < 20; i++) { - promises.push(Bun.$\`echo "Echo \${i}"\`.text().then(() => {})); - promises.push(Bun.$\`pwd\`.text().then(() => {})); - promises.push(Bun.$\`true\`.text().then(() => {})); - } - - await Promise.all(promises); - console.log("All builtin commands completed"); - `, - }); + const script = `const promises = []; for (let i = 0; i < 20; i++) { promises.push(Bun.$\`echo "Echo \${i}"\`.text().then(() => {})); promises.push(Bun.$\`pwd\`.text().then(() => {})); promises.push(Bun.$\`true\`.text().then(() => {})); } await Promise.all(promises); console.log("All builtin commands completed");`; await using proc = Bun.spawn({ - cmd: [bunExe(), "run", "test.ts"], - cwd: String(dir), + cmd: [bunExe(), "-e", script], env: bunEnv, stderr: "pipe", stdout: "pipe", @@ -102,8 +53,24 @@ test("shell commands with mixed builtins should not crash", async () => { const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); - expect(stderr).not.toContain("Segmentation fault"); - expect(stderr).not.toContain("panic"); expect(stdout).toContain("All builtin commands completed"); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); +}); + +test("concurrent shell commands with failing commands should handle errors", async () => { + // Test that failing commands are handled correctly and don't cause crashes + const script = `const promises = []; for (let i = 0; i < 20; i++) { if (i % 3 === 0) { promises.push(Bun.$\`nonexistent_command_\${i}\`.nothrow().text().catch(() => "failed")); } else { promises.push(Bun.$\`echo "Success \${i}"\`.text()); } } const results = await Promise.all(promises); console.log("Handled", results.length, "commands"); console.log("Error handling completed");`; + + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", script], + env: bunEnv, + stderr: "pipe", + stdout: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stdout).toContain("Error handling completed"); expect(exitCode).toBe(0); });