diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index e5020e068d..c0dbd7b755 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -6966,9 +6966,9 @@ declare module "bun" { /** * The user ID to run the subprocess as (Linux only). - * + * * On macOS and Windows, this option is silently ignored. - * + * * @platform Linux * @example * ```ts @@ -6983,9 +6983,9 @@ declare module "bun" { /** * The group ID to run the subprocess as (Linux only). - * + * * On macOS and Windows, this option is silently ignored. - * + * * @platform Linux * @example * ```ts diff --git a/test/js/bun/spawn/spawn-uid-gid.test.ts b/test/js/bun/spawn/spawn-uid-gid.test.ts index 47e507d28a..635695b897 100644 --- a/test/js/bun/spawn/spawn-uid-gid.test.ts +++ b/test/js/bun/spawn/spawn-uid-gid.test.ts @@ -1,96 +1,96 @@ -import { test, expect, describe } from "bun:test"; -import { bunExe, bunEnv, isPosix, isLinux, isMacOS } from "harness"; +import { describe, expect, test } from "bun:test"; +import { bunEnv, bunExe, isLinux, isMacOS } from "harness"; // uid/gid support is Linux-only due to macOS security restrictions describe.if(isLinux)("Bun.spawn with uid and gid (Linux only)", () => { - // This test can only run as root, as only root can change user/group - test.if(process.getuid() === 0)("should spawn process with different uid and gid", async () => { - // 'nobody' user usually has a high UID, a safe non-root user. - // On macOS it's often -2 (65534), on Linux 65534. - const nobodyUser = await Bun.spawn({ cmd: ["id", "-u", "nobody"] }); - const nobodyUid = parseInt(await new Response(nobodyUser.stdout).text()); - - const nobodyGroup = await Bun.spawn({ cmd: ["id", "-g", "nobody"] }); - const nobodyGid = parseInt(await new Response(nobodyGroup.stdout).text()); + // This test can only run as root, as only root can change user/group + test.if(process.getuid() === 0)("should spawn process with different uid and gid", async () => { + // 'nobody' user usually has a high UID, a safe non-root user. + // On macOS it's often -2 (65534), on Linux 65534. + const nobodyUser = await Bun.spawn({ cmd: ["id", "-u", "nobody"] }); + const nobodyUid = parseInt(await new Response(nobodyUser.stdout).text()); - // Test with spawn (async) - const procUid = Bun.spawn({ - cmd: [bunExe(), "-e", "console.write(String(process.getuid()))"], - env: bunEnv, - uid: nobodyUid, - }); - const uidOutput = await new Response(procUid.stdout).text(); - expect(parseInt(uidOutput)).toBe(nobodyUid); - expect(await procUid.exited).toBe(0); - - const procGid = Bun.spawn({ - cmd: [bunExe(), "-e", "console.write(String(process.getgid()))"], - env: bunEnv, - gid: nobodyGid, - }); - const gidOutput = await new Response(procGid.stdout).text(); - expect(parseInt(gidOutput)).toBe(nobodyGid); - expect(await procGid.exited).toBe(0); - - // Test with spawnSync - const { stdout: syncUidOut } = Bun.spawnSync({ - cmd: [bunExe(), "-e", "console.write(String(process.getuid()))"], - env: bunEnv, - uid: nobodyUid, - }); - expect(parseInt(syncUidOut.toString())).toBe(nobodyUid); - }); + const nobodyGroup = await Bun.spawn({ cmd: ["id", "-g", "nobody"] }); + const nobodyGid = parseInt(await new Response(nobodyGroup.stdout).text()); - test("should fail with EPERM when not running as root", async () => { - // Skip if running as root, as this test would pass. - if (process.getuid() === 0) { - return; - } + // Test with spawn (async) + const procUid = Bun.spawn({ + cmd: [bunExe(), "-e", "console.write(String(process.getuid()))"], + env: bunEnv, + uid: nobodyUid, + }); + const uidOutput = await new Response(procUid.stdout).text(); + expect(parseInt(uidOutput)).toBe(nobodyUid); + expect(await procUid.exited).toBe(0); - const targetUid = process.getuid() + 1; // Any other UID - - // Bun.spawn throws a system error on failure - expect(() => { - Bun.spawnSync({ - cmd: ["echo", "hello"], - uid: targetUid, - }); - }).toThrow("operation not permitted"); + const procGid = Bun.spawn({ + cmd: [bunExe(), "-e", "console.write(String(process.getgid()))"], + env: bunEnv, + gid: nobodyGid, }); - - test("should throw for invalid uid/gid arguments", () => { - expect(() => { - Bun.spawnSync({ cmd: ["echo", "hello"], uid: "not-a-number" }); - }).toThrow("Invalid value for option \"uid\""); - - expect(() => { - Bun.spawnSync({ cmd: ["echo", "hello"], gid: -1 }); - }).toThrow("Invalid value for option \"gid\""); + const gidOutput = await new Response(procGid.stdout).text(); + expect(parseInt(gidOutput)).toBe(nobodyGid); + expect(await procGid.exited).toBe(0); + + // Test with spawnSync + const { stdout: syncUidOut } = Bun.spawnSync({ + cmd: [bunExe(), "-e", "console.write(String(process.getuid()))"], + env: bunEnv, + uid: nobodyUid, }); + expect(parseInt(syncUidOut.toString())).toBe(nobodyUid); + }); + + test("should fail with EPERM when not running as root", async () => { + // Skip if running as root, as this test would pass. + if (process.getuid() === 0) { + return; + } + + const targetUid = process.getuid() + 1; // Any other UID + + // Bun.spawn throws a system error on failure + expect(() => { + Bun.spawnSync({ + cmd: ["echo", "hello"], + uid: targetUid, + }); + }).toThrow("operation not permitted"); + }); + + test("should throw for invalid uid/gid arguments", () => { + expect(() => { + Bun.spawnSync({ cmd: ["echo", "hello"], uid: "not-a-number" }); + }).toThrow('Invalid value for option "uid"'); + + expect(() => { + Bun.spawnSync({ cmd: ["echo", "hello"], gid: -1 }); + }).toThrow('Invalid value for option "gid"'); + }); }); // Test that uid/gid is silently ignored on macOS describe.if(isMacOS)("Bun.spawn with uid and gid (macOS)", () => { - test("should silently ignore uid/gid on macOS", async () => { - const currentUid = process.getuid(); - const currentGid = process.getgid(); - - // Test with spawn (async) - should ignore uid/gid and run as current user - const procUid = Bun.spawn({ - cmd: [bunExe(), "-e", "console.write(String(process.getuid()))"], - env: bunEnv, - uid: 9999, // Some arbitrary uid that would fail if actually used - }); - const uidOutput = await new Response(procUid.stdout).text(); - expect(parseInt(uidOutput)).toBe(currentUid); - expect(await procUid.exited).toBe(0); - - // Test with spawnSync - should ignore gid and run as current group - const { stdout: gidOut } = Bun.spawnSync({ - cmd: [bunExe(), "-e", "console.write(String(process.getgid()))"], - env: bunEnv, - gid: 9999, // Some arbitrary gid that would fail if actually used - }); - expect(parseInt(gidOut.toString())).toBe(currentGid); + test("should silently ignore uid/gid on macOS", async () => { + const currentUid = process.getuid(); + const currentGid = process.getgid(); + + // Test with spawn (async) - should ignore uid/gid and run as current user + const procUid = Bun.spawn({ + cmd: [bunExe(), "-e", "console.write(String(process.getuid()))"], + env: bunEnv, + uid: 9999, // Some arbitrary uid that would fail if actually used }); -}); \ No newline at end of file + const uidOutput = await new Response(procUid.stdout).text(); + expect(parseInt(uidOutput)).toBe(currentUid); + expect(await procUid.exited).toBe(0); + + // Test with spawnSync - should ignore gid and run as current group + const { stdout: gidOut } = Bun.spawnSync({ + cmd: [bunExe(), "-e", "console.write(String(process.getgid()))"], + env: bunEnv, + gid: 9999, // Some arbitrary gid that would fail if actually used + }); + expect(parseInt(gidOut.toString())).toBe(currentGid); + }); +}); diff --git a/test/napi/napi.test.ts b/test/napi/napi.test.ts index 783a789198..e4abc9c071 100644 --- a/test/napi/napi.test.ts +++ b/test/napi/napi.test.ts @@ -275,18 +275,23 @@ describe("napi", () => { runOn("node", "test_napi_async_work_complete_null_check", []), runOn(bunExe(), "test_napi_async_work_complete_null_check", []), ]); - + // Filter out debug logs and normalize - const cleanBunResult = bunResult - .replaceAll(/^\[\w+\].+$/gm, "") - .trim(); - + const cleanBunResult = bunResult.replaceAll(/^\[\w+\].+$/gm, "").trim(); + // Both should contain these two lines, but order may vary const expectedLines = ["execute called!", "resolved to undefined"]; - - const nodeLines = nodeResult.trim().split('\n').filter(line => line).sort(); - const bunLines = cleanBunResult.split('\n').filter(line => line).sort(); - + + const nodeLines = nodeResult + .trim() + .split("\n") + .filter(line => line) + .sort(); + const bunLines = cleanBunResult + .split("\n") + .filter(line => line) + .sort(); + expect(bunLines).toEqual(nodeLines); expect(bunLines).toEqual(expectedLines.sort()); });