Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions scripts/runner.node.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
writeFileSync,
} from "node:fs";
import { readFile } from "node:fs/promises";
import { availableParallelism, userInfo } from "node:os";
import { availableParallelism } from "node:os";
import { basename, dirname, extname, join, relative, sep } from "node:path";
import { createInterface } from "node:readline";
import { setTimeout as setTimeoutPromise } from "node:timers/promises";
Expand All @@ -54,6 +54,7 @@ import {
getOs,
getSecret,
getShell,
getUserInfo,
getWindowsExitReason,
isAndroid,
isBuildkite,
Expand Down Expand Up @@ -844,7 +845,14 @@ async function runTests() {
const absoluteTestPath = join(testsPath, testPath);
const title = relative(cwd, absoluteTestPath).replaceAll(sep, "/");
if (isNodeTest(testPath)) {
const testContent = readFileSync(absoluteTestPath, "utf-8");
let testContent = "";
try {
testContent = readFileSync(absoluteTestPath, "utf-8");
} catch (error) {
// Gone since discovery (a wiped checkout). The step below fails on
// it with bun's own error instead of this throw ending the whole run.
if (error?.code !== "ENOENT") throw error;
}
Comment thread
robobun marked this conversation as resolved.
const flagsMatch = /^\/\/ Flags:[^\S\r\n]+(--[^\r\n]*)$/m.exec(testContent);
const testFlags = flagsMatch
? flagsMatch[1].split(/\s+/).filter(flag => resolutionGatingFlags.has(flag.split("=")[0]))
Expand Down Expand Up @@ -1762,7 +1770,7 @@ function getCombinedPath(execPath) {
async function spawnBun(execPath, { args, cwd, timeout, gracefulTimeout, idleTimeout, env, stdout, stderr }) {
const path = getCombinedPath(execPath);
const tmpdirPath = mkdtempSync(join(tmpdir(), "buntmp-"));
const { username, homedir } = userInfo();
const { username, homedir } = getUserInfo();
const shellPath = getShell();
const bunEnv = {
...process.env,
Expand Down
37 changes: 34 additions & 3 deletions scripts/utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1609,11 +1609,42 @@ export function getHostname() {
}

/**
* @returns {string}
* @typedef {object} UserInfo
* @property {string | undefined} username
* @property {string | undefined} homedir
*/

/** @type {UserInfo | undefined} */
let cachedUserInfo;

/**
* The user that runs this process, looked up once. `os.userInfo()` is a
* getpwuid_r() lookup that can fail at runtime: a macOS host that is shutting
* down answers ENOENT for its own uid once opendirectoryd is gone. The login
* session exported the same values, so a failed lookup falls back to them.
* @returns {UserInfo}
*/
export function getUserInfo() {
if (!cachedUserInfo) {
try {
const { username, homedir } = userInfo();
cachedUserInfo = { username, homedir };
} catch (error) {
cachedUserInfo = {
username: getEnv("USER", false) || getEnv("LOGNAME", false) || getEnv("USERNAME", false),
homedir: getEnv("HOME", false) || getEnv("USERPROFILE", false),
};
console.warn("os.userInfo() failed, using USER and HOME from the environment instead:", error);
}
}
return cachedUserInfo;
}

/**
* @returns {string | undefined}
*/
export function getUsername() {
const { username } = userInfo();
return username;
return getUserInfo().username;
}

/**
Expand Down
35 changes: 35 additions & 0 deletions test/internal/runner-user-info.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* scripts/runner.node.mjs sets USER and HOME for every `bun test` it spawns
* from getUserInfo() in scripts/utils.mjs. spawnBun used to call os.userInfo()
* per test file instead, and that call throws (`uv_os_get_passwd returned
* ENOENT`) on a macOS agent whose host has begun to shut down, which ended the
* whole shard with exit 1. getUserInfo() looks the user up once and falls back
* to the environment when the lookup fails.
*/
import { expect, mock, test } from "bun:test";
import * as os from "node:os";
import { getUserInfo, getUsername } from "../../scripts/utils.mjs";

test("getUserInfo() falls back to USER and HOME when os.userInfo() throws, and looks the user up once", () => {
const real = { ...os };
let calls = 0;
mock.module("node:os", () => ({
...real,
userInfo: () => {
calls++;
throw new Error("A system error occurred: uv_os_get_passwd returned ENOENT (no such file or directory)");
},
}));
try {
const { env } = process;
const expected = { username: env.USER || env.LOGNAME || env.USERNAME, homedir: env.HOME || env.USERPROFILE };

const first = getUserInfo();
expect(first).toEqual(expected);
expect(getUserInfo()).toBe(first);
expect(getUsername()).toBe(expected.username);
expect(calls).toBe(1);
} finally {
mock.module("node:os", () => real);
}
});
Loading