Skip to content
Draft
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
12 changes: 8 additions & 4 deletions scripts/test-run-lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import {
statSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { homedir } from "node:os";
import { join } from "node:path";

export const TEST_RUN_ID_ENV = "OCX_TEST_RUN_ID";
export const TEST_RUN_NO_QUEUE_ENV = "OCX_TEST_NO_QUEUE";
const DEFAULT_LOCK_PATH = join(tmpdir(), "opencodex-bun-test.lock");
const OWNER_FILE = "owner.json";
const MEMBERS_DIR = "members";
const INCOMPLETE_OWNER_GRACE_MS = 10_000;
Expand Down Expand Up @@ -48,6 +47,11 @@ export interface BareTestRunIdentity {
runId: string;
}

/** Keep the shared lock in a directory controlled by the current OS user. */
export function resolveDefaultTestRunLockPath(home = homedir()): string {
return join(home, ".opencodex-bun-test.lock");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the PID-based lock on a host-local filesystem

When the same home directory is mounted on multiple hosts, as with NFS/SMB enterprise homes or CI containers sharing a home volume, this path makes those hosts operate on one lock even though processIsAlive() checks PIDs only on the local host. A second host can therefore reclaim an active lock because its local PID is absent and run a suite concurrently, or wait 45 minutes because an unrelated local process has the same PID. Use a user-owned host-local directory, or include a stable host identity in the lock namespace, so the lock's filesystem scope matches its PID-liveness scope.

AGENTS.md reference: scripts/AGENTS.md:L14-L15

Useful? React with 👍 / 👎.

}

/**
* Give one bare Bun invocation a stable identity without conflating sibling commands.
*
Expand Down Expand Up @@ -151,7 +155,7 @@ function ownsLock(lockPath: string, owner: TestRunLockOwner): boolean {
}

/**
* Acquire the machine-wide OpenCodex Bun-test lock.
* Acquire the user-wide OpenCodex Bun-test lock.
*
* `mkdir` is the cross-platform atomic primitive. The owner PID makes a lock left by
* SIGKILL recoverable, while the run ID lets every worker belonging to one bare
Expand All @@ -165,7 +169,7 @@ export async function acquireTestRunLock(options: AcquireTestRunLockOptions): Pr
return { acquired: false, owner: null, release() {} };
}

const lockPath = options.lockPath ?? DEFAULT_LOCK_PATH;
const lockPath = options.lockPath ?? resolveDefaultTestRunLockPath();
const ownerPid = options.ownerPid ?? process.pid;
const pollMs = Math.max(1, options.pollMs ?? 5_000);
const maxWaitMs = Math.max(pollMs, options.maxWaitMs ?? 45 * 60 * 1000);
Expand Down
4 changes: 2 additions & 2 deletions scripts/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,10 @@ if (import.meta.main) {
const lock = await acquireTestRunLock({
runId,
onWait: owner => console.warn(
`[test] another Bun test run${owner ? ` (pid ${owner.pid})` : ""} holds the machine lock; waiting. `
`[test] another Bun test run${owner ? ` (pid ${owner.pid})` : ""} holds the user lock; waiting. `
+ "Set OCX_TEST_NO_QUEUE=1 only for intentional overlap.",
),
onAcquiredAfterWait: elapsedMs => console.warn(`[test] acquired the machine lock after ${Math.round(elapsedMs / 1000)}s.`),
onAcquiredAfterWait: elapsedMs => console.warn(`[test] acquired the user lock after ${Math.round(elapsedMs / 1000)}s.`),
});
const startedAt = Date.now();
try {
Expand Down
2 changes: 1 addition & 1 deletion tests/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ await acquireTestRunLock({
runId,
ownerPid: bareIdentity.ownerPid,
onWait: owner => console.warn(
`[test] bare Bun worker ${process.pid} is waiting for test run${owner ? ` pid ${owner.pid}` : ""} to release the machine lock.`,
`[test] bare Bun worker ${process.pid} is waiting for test run${owner ? ` pid ${owner.pid}` : ""} to release the user lock.`,
),
});

Expand Down
8 changes: 7 additions & 1 deletion tests/test-runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "../scripts/test";
import {
acquireTestRunLock,
resolveDefaultTestRunLockPath,
resolveBareTestRunIdentity,
TEST_RUN_NO_QUEUE_ENV,
} from "../scripts/test-run-lock";
Expand Down Expand Up @@ -208,7 +209,12 @@ describe("bun test argv", () => {
});
});

describe("bun test machine lock", () => {
describe("bun test user lock", () => {
test("keeps the default lock out of the shared temporary directory", () => {
expect(resolveDefaultTestRunLockPath(join("home", "alice")))
.toBe(join("home", "alice", ".opencodex-bun-test.lock"));
});

test("independent bare runners do not inherit a shared long-lived parent identity", () => {
expect(resolveBareTestRunIdentity({ pid: 101, ppid: 50 })).toEqual({
ownerPid: 101,
Expand Down
Loading