mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
This commit is contained in:
committed by
Meghan Denny
parent
aea99c1757
commit
02780eb9be
@@ -272,12 +272,13 @@ function execFile(file, args, options, callback) {
|
||||
// merge chunks
|
||||
let stdout;
|
||||
let stderr;
|
||||
if (child.stdout?.readableEncoding) {
|
||||
if (encoding || child.stdout?.readableEncoding) {
|
||||
stdout = ArrayPrototypeJoin.$call(_stdout, "");
|
||||
} else {
|
||||
stdout = BufferConcat(_stdout);
|
||||
}
|
||||
if (child.stderr?.readableEncoding) {
|
||||
|
||||
if (encoding || child.stderr?.readableEncoding) {
|
||||
stderr = ArrayPrototypeJoin.$call(_stderr, "");
|
||||
} else {
|
||||
stderr = BufferConcat(_stderr);
|
||||
|
||||
116
test/regression/issue/20753.test.js
Normal file
116
test/regression/issue/20753.test.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { isWindows } from "harness";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
describe.skipIf(isWindows /* accessing posix-specific paths */)("stdout should always be a string", () => {
|
||||
test("execFile returns string stdout/stderr even when process fails to spawn", done => {
|
||||
// Test case that would cause the issue: non-existent command
|
||||
execFile("/does/not/exist", [], (err, stdout, stderr) => {
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.code).toBe("ENOENT");
|
||||
|
||||
// These should never be undefined - they should be strings by default
|
||||
expect(stdout).toBeDefined();
|
||||
expect(stderr).toBeDefined();
|
||||
expect(typeof stdout).toBe("string");
|
||||
expect(typeof stderr).toBe("string");
|
||||
expect(stdout).toBe("");
|
||||
expect(stderr).toBe("");
|
||||
|
||||
// This is what claude-code was trying to do that failed
|
||||
expect(() => stdout.trim()).not.toThrow();
|
||||
expect(() => stderr.trim()).not.toThrow();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("execFile returns string stdout/stderr for permission denied errors", done => {
|
||||
// Another edge case: file exists but not executable
|
||||
execFile("/etc/passwd", [], (err, stdout, stderr) => {
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.code).toBe("EACCES");
|
||||
|
||||
expect(stdout).toBeDefined();
|
||||
expect(stderr).toBeDefined();
|
||||
expect(typeof stdout).toBe("string");
|
||||
expect(typeof stderr).toBe("string");
|
||||
expect(stdout).toBe("");
|
||||
expect(stderr).toBe("");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("execFile returns Buffer stdout/stderr when encoding is 'buffer'", done => {
|
||||
execFile("/does/not/exist", [], { encoding: "buffer" }, (err, stdout, stderr) => {
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.code).toBe("ENOENT");
|
||||
|
||||
expect(stdout).toBeDefined();
|
||||
expect(stderr).toBeDefined();
|
||||
expect(Buffer.isBuffer(stdout)).toBe(true);
|
||||
expect(Buffer.isBuffer(stderr)).toBe(true);
|
||||
expect(stdout.length).toBe(0);
|
||||
expect(stderr.length).toBe(0);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("execFile promisified version includes stdout/stderr in error object", async () => {
|
||||
try {
|
||||
await execFileAsync("/does/not/exist", []);
|
||||
expect.unreachable("Should have thrown");
|
||||
} catch (err) {
|
||||
expect(err.code).toBe("ENOENT");
|
||||
|
||||
// Promisified version attaches stdout/stderr to the error object
|
||||
expect(err.stdout).toBeDefined();
|
||||
expect(err.stderr).toBeDefined();
|
||||
expect(typeof err.stdout).toBe("string");
|
||||
expect(typeof err.stderr).toBe("string");
|
||||
expect(err.stdout).toBe("");
|
||||
expect(err.stderr).toBe("");
|
||||
}
|
||||
});
|
||||
|
||||
test("execFile returns stdout/stderr for process that exits with error code", done => {
|
||||
execFile(
|
||||
process.execPath,
|
||||
["-e", "console.log('output'); console.error('error'); process.exit(1)"],
|
||||
(err, stdout, stderr) => {
|
||||
expect(err).toBeTruthy();
|
||||
expect(err.code).toBe(1);
|
||||
|
||||
expect(stdout).toBeDefined();
|
||||
expect(stderr).toBeDefined();
|
||||
expect(typeof stdout).toBe("string");
|
||||
expect(typeof stderr).toBe("string");
|
||||
expect(stdout).toBe("output\n");
|
||||
expect(stderr).toBe("error\n");
|
||||
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("execFile handles fast-exiting processes correctly", done => {
|
||||
// Process that exits immediately
|
||||
execFile("true", [], (err, stdout, stderr) => {
|
||||
expect(err).toBeNull();
|
||||
|
||||
expect(stdout).toBeDefined();
|
||||
expect(stderr).toBeDefined();
|
||||
expect(typeof stdout).toBe("string");
|
||||
expect(typeof stderr).toBe("string");
|
||||
expect(stdout).toBe("");
|
||||
expect(stderr).toBe("");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user