Skip to content
Merged
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

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions tests/codex-integration/codex-composed-acceptance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,19 @@ import {
resolveEffectiveUserIdentity,
} from "../../src/codex/user-identity";
import { claimOwnedServiceHome, withOwnedServiceHomePreload } from "../helpers/owned-service-home";
import { SERVER_BUDGET_MS } from "../helpers/test-budget";
import { INTERNAL_DEADLINE_MS, SERVER_BUDGET_MS } from "../helpers/test-budget";
import { repoRoot as resolveRepoRoot } from "../helpers/repo-root";

/**
* Bound for a request the fixture deliberately HOLDS open: the provider's /models response
* blocks until the test calls release(), so this request's ceiling is "a gather held
* across one overlapping mutation", not a single round-trip. On run 33930757649 the plain
* SERVER_BUDGET_MS abort fired at 30 s while the case sat at 57.7 s total and its siblings
* passed at 47.9 s and 57.8 s — the case was inside its band, the per-request bound was
* not. Named rather than multiplied so the next reader sees WHAT is being bounded.
*/
const HELD_REQUEST_BUDGET_MS = SERVER_BUDGET_MS + INTERNAL_DEADLINE_MS;

const repoRoot = resolveRepoRoot();
const cliPath = resolve(repoRoot, "src/cli/index.ts");
const lockChildPath = resolve(repoRoot, "tests/helpers/codex-write-lock-child.ts");
Expand Down Expand Up @@ -490,8 +500,8 @@ describe("WP13 composed toggle acceptance", () => {
} }, defaultProvider: "fixture", clientIntegrations: { codex: true } });
hold = true;
// This request is intentionally held open while a second real HTTP
// mutation crosses the Windows process-backed identity path.
const stale = fx.request(server.runtime, "/api/sync", { method: "POST" }, SERVER_BUDGET_MS);
// mutation crosses the Windows process-backed identity path; see HELD_REQUEST_BUDGET_MS.
const stale = fx.request(server.runtime, "/api/sync", { method: "POST" }, HELD_REQUEST_BUDGET_MS);
await Promise.race([
enteredGather,
stale.then(result => Promise.reject(new Error(
Expand Down
7 changes: 5 additions & 2 deletions tests/codex-integration/codex-history-lock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../../src/codex/history-lock";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { repoRoot as resolveRepoRoot } from "../helpers/repo-root";
import { INTERNAL_DEADLINE_MS, SPAWN_BUDGET_MS } from "../helpers/test-budget";

const repoRoot = resolveRepoRoot();
const sandboxes: string[] = [];
Expand Down Expand Up @@ -56,7 +57,9 @@ afterEach(() => {
for (const root of sandboxes.splice(0)) removeTreeWithRetry(root);
});

async function waitForPath(path: string, timeoutMs = 10_000): Promise<void> {
// Same shape as codex-write-lock: gates on a spawned child reaching its marker, which
// costs 8-19 s on windows-latest (run 33930757649). Local stays at 10 s.
async function waitForPath(path: string, timeoutMs = INTERNAL_DEADLINE_MS): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (!existsSync(path)) {
if (Date.now() > deadline) throw new Error(`timed out waiting for ${path}`);
Expand Down Expand Up @@ -107,7 +110,7 @@ test("H excludes a second process across the whole history unit", async () => {
// Once the holder is gone the lock is available again.
const after = withHistoryWriteSerialization(sandbox.codexHome, sandbox.stateDb, () => "ok");
expect(after).toEqual({ kind: "completed", value: "ok" });
}, 30_000);
}, SPAWN_BUDGET_MS);

test("a permit is refused once its acquisition released, and for a foreign state database", () => {
const sandbox = makeSandbox("ocx-history-permit-");
Expand Down
6 changes: 4 additions & 2 deletions tests/codex-integration/codex-history-worker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "../../src/codex/history-worker";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { repoRoot as resolveRepoRoot } from "../helpers/repo-root";
import { INTERNAL_DEADLINE_MS, SPAWN_BUDGET_MS } from "../helpers/test-budget";

// A held write lock otherwise costs the full production 5s busy timeout per
// attempt, tripping bun's 5s default per-test timeout.
Expand Down Expand Up @@ -343,7 +344,8 @@ test("a second holder of H makes the unit report blocked rather than wait", asyn
`], { cwd: repoRoot, env: fixture.env, stdout: "pipe", stderr: "pipe" });

try {
const deadline = Date.now() + 10_000;
// The holder is a spawned child; 8-19 s to boot on windows-latest (run 33930757649).
const deadline = Date.now() + INTERNAL_DEADLINE_MS;
while (!existsSync(ready)) {
if (Date.now() > deadline) throw new Error("holder never acquired H");
await Bun.sleep(5);
Expand All @@ -366,7 +368,7 @@ test("a second holder of H makes the unit report blocked rather than wait", asyn
writeFileSync(release, "release");
expect(await holder.exited).toBe(0);
}
}, 30_000);
}, SPAWN_BUDGET_MS);

/**
* The reason the parent can tell a false "app holds the DB" from a real one:
Expand Down
5 changes: 4 additions & 1 deletion tests/codex-integration/codex-inject-write-lock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { SPAWN_BUDGET_MS } from "../helpers/test-budget";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { repoRoot as resolveRepoRoot } from "../helpers/repo-root";
import { INTERNAL_DEADLINE_MS } from "../helpers/test-budget";

const repoRoot = resolveRepoRoot();
const CHILD = join(repoRoot, "tests", "helpers", "codex-inject-race-child.ts");
Expand Down Expand Up @@ -340,7 +341,9 @@ describe("the lock is on the production path", () => {
let cleanupFailed = false;
let cleanupError: unknown;
try {
const deadline = Date.now() + 10_000;
// Each poll iteration spawns a real child; the hold marker comes from another one.
// 8-19 s per boot on windows-latest (run 33930757649).
const deadline = Date.now() + INTERNAL_DEADLINE_MS;
while (!existsSync(holdMarker) && Date.now() < deadline) {
requireChildSuccess(runChild(["--eval", "Bun.sleepSync(20)"], process.env), "hold-marker wait child");
}
Expand Down
6 changes: 5 additions & 1 deletion tests/codex-integration/codex-prompt-route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import type { ManagementPrincipal } from "../../src/server/management-auth";
import type { OcxConfig } from "../../src/types";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { INTERNAL_DEADLINE_MS } from "../helpers/test-budget";

const MARKER = "# Auto-injected by opencodex";
const config = { port: 10100, defaultProvider: "openai", providers: {} } as OcxConfig;
Expand Down Expand Up @@ -71,7 +72,10 @@ function read(path: string): string | null {
}

async function waitUntil(predicate: () => boolean, detail: string): Promise<void> {
const deadline = Date.now() + 5_000;
// Every caller gates on a spawned probe child writing a pid/start marker: 8-19 s to boot
// on windows-latest (run 33930757649). The diagnostic below names the marker, so the
// bound must stay under the enclosing case budget for it to be what gets reported.
const deadline = Date.now() + INTERNAL_DEADLINE_MS;
while (!predicate()) {
if (Date.now() >= deadline) throw new Error(`timed out waiting for ${detail}`);
await Bun.sleep(10);
Expand Down
4 changes: 3 additions & 1 deletion tests/codex-integration/codex-prompt-text-probe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
setPromptTextProbeCommandForTests,
} from "../../src/codex/prompt-text-probe";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { INTERNAL_DEADLINE_MS } from "../helpers/test-budget";

const lifecycleRoots: string[] = [];
const VALID_PROBE_OUTPUT = JSON.stringify([{
Expand All @@ -32,7 +33,8 @@ function message(text: string): string {
}

async function waitUntil(predicate: () => boolean, detail: string): Promise<void> {
const deadline = Date.now() + 5_000;
// Gates on a spawned child writing its pid marker or exiting: 8-19 s on windows-latest.
const deadline = Date.now() + INTERNAL_DEADLINE_MS;
while (!predicate()) {
if (Date.now() >= deadline) throw new Error(`timed out waiting for ${detail}`);
await Bun.sleep(10);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { claimOwnedServiceHome, withOwnedServiceHomePreload } from "../helpers/o
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { repoRoot as resolveRepoRoot } from "../helpers/repo-root";
import { SPAWN_BUDGET_MS } from "../helpers/test-budget";
import { INTERNAL_DEADLINE_MS } from "../helpers/test-budget";

const repoRoot = resolveRepoRoot();
const sandboxes: Sandbox[] = [];
Expand Down Expand Up @@ -200,7 +201,7 @@ async function holdCatalogLock(sandbox: Sandbox): Promise<{
});
sandbox.children.add(child);
sandbox.releaseMarkers.add(release);
await waitForPath(ready, 12_000);
await waitForPath(ready, INTERNAL_DEADLINE_MS);
return {
release: () => { try { writeFileSync(release, "release"); } catch { /* teardown may have released already */ } },
child,
Expand Down Expand Up @@ -370,7 +371,7 @@ for (const publisher of ["convergence", "retained"] as const) {
`], sandbox.preloadPath)], { cwd: repoRoot, env: sandboxChildEnv(sandbox), stdout: "pipe", stderr: "pipe" });
sandbox.children.add(sync);

await raceBarrier(sync, waitForPath(requested, 16_000));
await raceBarrier(sync, waitForPath(requested, INTERNAL_DEADLINE_MS));
const published = await runPublisher(sandbox, publisher, config);
if (published.exitCode !== 0) {
throw new Error(`${publisher} publisher failed\nstdout=${published.stdout}\nstderr=${published.stderr}`);
Expand Down Expand Up @@ -447,7 +448,7 @@ test("a persisted runtime selection moved by another process during the await bl
`], sandbox.preloadPath)], { cwd: repoRoot, env: sandboxChildEnv(sandbox), stdout: "pipe", stderr: "pipe" });
sandbox.children.add(sync);

await raceBarrier(sync, waitForPath(requested, 16_000));
await raceBarrier(sync, waitForPath(requested, INTERNAL_DEADLINE_MS));

// Another process selects a different Codex runtime. No catalog byte changes.
writeFileSync(runtimeStatePath, `${JSON.stringify({
Expand Down Expand Up @@ -511,7 +512,8 @@ test("two processes at the post-approval management seam serialize instead of in
// looked exactly like a production defect until the encoder said so.
globalThis.fetch = async () => {
writeFileSync(${JSON.stringify(barrier)} + "-" + ${JSON.stringify(marker)}, "here");
const deadline = Date.now() + 8000;
// Two children rendezvous on markers; either may take 8-19 s to boot on windows-latest.
const deadline = Date.now() + ${INTERNAL_DEADLINE_MS};
while (Date.now() < deadline) {
if (existsSync(${JSON.stringify(barrier)} + "-a") && existsSync(${JSON.stringify(barrier)} + "-b")) break;
await Bun.sleep(5);
Expand Down Expand Up @@ -551,7 +553,8 @@ test("two processes at the post-approval management seam serialize instead of in
// On macOS CI both children can still lose the config lock before approval even
// after the warm-up — that proves nothing about catalog serialization. Retry
// vacuous runs until at least one process reaches the post-approval seam.
const attemptDeadline = Date.now() + 20_000;
// Each attempt boots two real children; bound the retry loop by the spawn budget, not a literal.
const attemptDeadline = Date.now() + SPAWN_BUDGET_MS;
let results: Array<{ exitCode: number; stdout: string; stderr: string }> | undefined;
while (Date.now() < attemptDeadline) {
for (const marker of ["a", "b"] as const) {
Expand Down
4 changes: 3 additions & 1 deletion tests/codex-integration/codex-shim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { tmpdir } from "node:os";
import { autoRestoreCodexShim, buildUnixCodexShim, buildWindowsCodexShim, buildWindowsPowerShellCodexShim, diagnoseCodexShim, findCodexOnPath, inspectCodexShimBackingForCommand, installCodexShim, isLocalAbsoluteInspectionPath, isVersionManagerOwnedCodexPath, isWindowsInteropDir, lastCodexDiscoveryError, setCodexShimFreshWriteHookForTests, setCodexShimGuardedWriteHookForTests, setCodexShimProbeHookForTests, setCodexShimProbeObservationMsForTests, setCodexShimProbeShellForTests, setCodexShimRollbackRestoreHookForTests, uninstallCodexShim } from "../../src/codex/shim";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { repoPath, repoRoot } from "../helpers/repo-root";
import { INTERNAL_DEADLINE_MS } from "../helpers/test-budget";

const SHIM_MARKER = "opencodex codex autostart shim";
const UNIX_SHIM_REVISION_MARKER = "opencodex unix codex shim revision 2";
Expand Down Expand Up @@ -1701,7 +1702,8 @@ exit 127
stdout: "pipe",
stderr: "pipe",
});
const deadline = Date.now() + 5_000;
// Spawned holder child writing its ready marker: 8-19 s on windows-latest.
const deadline = Date.now() + INTERNAL_DEADLINE_MS;
while (!existsSync(readyPath) && Date.now() < deadline) await Bun.sleep(5);
expect(existsSync(readyPath)).toBe(true);

Expand Down
15 changes: 10 additions & 5 deletions tests/codex-integration/codex-write-lock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import type { AdmissionSnapshot } from "../../src/codex/convergence-types";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { helperPath } from "../helpers/repo-root";
import { INTERNAL_DEADLINE_MS, SPAWN_BUDGET_MS } from "../helpers/test-budget";

let root = "";
let codexHome = "";
Expand Down Expand Up @@ -311,7 +312,11 @@ describe("two real processes contend for one lock", () => {
return JSON.parse(line) as { status: string; reason?: string; value?: string; lockId?: string };
}

async function waitFor(path: string, timeoutMs = 10_000): Promise<void> {
// A spawned holder child boots in 8-19 s on a loaded windows-latest shard; the 10 s
// literal expired first on run 33930757649 ("case 0", 10.67 s). INTERNAL_DEADLINE_MS is
// the named bound for an in-test wait and stays under the enclosing SPAWN_BUDGET_MS so
// this helper's "timed out waiting for" diagnostic is what gets reported, not Bun's.
async function waitFor(path: string, timeoutMs = INTERNAL_DEADLINE_MS): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if (Bun.file(path).size > 0) return;
Expand Down Expand Up @@ -340,14 +345,14 @@ describe("two real processes contend for one lock", () => {
// was contention rather than a permanent refusal wearing its label.
const after = await withCodexWriteLock(options({ timeoutMs: 5_000 }), publishing("parent"));
expect(after.status).toBe("acquired");
}, 30_000);
}, SPAWN_BUDGET_MS);

test("both processes resolve the same lock id for one home", async () => {
const first = await childResult(spawnChild({ timeoutMs: 5_000 }));
expect(first.status).toBe("acquired");
const local = canonicalizeCodexHome(codexHome);
expect(local.ok && first.lockId).toBe(local.ok ? local.home.lockId : "x");
}, 30_000);
}, SPAWN_BUDGET_MS);

/**
* A waiting contender must actually wait rather than fail fast — and must
Expand All @@ -368,7 +373,7 @@ describe("two real processes contend for one lock", () => {
expect(holderResult.status).toBe("acquired");
expect(waited.status).toBe("acquired");
expect(waited.status === "acquired" && waited.waitedMs).toBeGreaterThan(0);
}, 30_000);
}, SPAWN_BUDGET_MS);

/**
* C7/C18 — the namespace keys on the OS user, not on any home accessor.
Expand Down Expand Up @@ -451,6 +456,6 @@ describe("two real processes contend for one lock", () => {
);
expect(after.status).toBe("acquired");
expect(after.lockId).toBe(held.lockId);
}, 30_000);
}, SPAWN_BUDGET_MS);
}
});
14 changes: 8 additions & 6 deletions tests/codex-integration/native-profile-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { NativeProfileError, type NativeProfileKey, type NativeProfileKeyProvide
import { codexCredentialMutationEpoch } from "../../src/codex/credential-mutation-epoch";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { helperPath, repoRoot } from "../helpers/repo-root";
import { INTERNAL_DEADLINE_MS, SPAWN_BUDGET_MS } from "../helpers/test-budget";

const roots: string[] = [];

Expand Down Expand Up @@ -137,7 +138,8 @@ async function leavePendingJournal(f: Awaited<ReturnType<typeof enrolledFixture>
* sized inside its 15 s test budget. On timeout the child's stderr is part of the error so
* a real crash is not mistaken for a slow start.
*/
async function waitForPath(path: string, child?: ReturnType<typeof Bun.spawn>, waitMs = 5_000): Promise<void> {
// Gates on a spawned child reaching its marker: 8-19 s on windows-latest (run 33930757649).
async function waitForPath(path: string, child?: ReturnType<typeof Bun.spawn>, waitMs = INTERNAL_DEADLINE_MS): Promise<void> {
const deadline = Date.now() + waitMs;
while (!existsSync(path) && Date.now() < deadline) await Bun.sleep(10);
if (existsSync(path)) return;
Expand Down Expand Up @@ -196,12 +198,12 @@ describe("native main profile transactions", () => {
const f = fixture();
const readyPath = join(f.root, "crash-ready");
const child = spawnLockHolder(f, readyPath, join(f.root, "unused-release"), { crash: true });
await waitForPath(readyPath, child, 12_000);
await waitForPath(readyPath, child, INTERNAL_DEADLINE_MS);
expect(await child.exited).toBe(87);

const successor = new NativeProfileManager({ ...f.options, lockWaitMs: 250 });
expect((await successor.recover(false)).recovered).toBe(false);
}, 15_000);
}, SPAWN_BUDGET_MS);

test("a losing same-process contender cannot release another transaction's POSIX lock", async () => {
if (process.platform === "win32") return;
Expand Down Expand Up @@ -252,7 +254,7 @@ describe("native main profile transactions", () => {
...(acquiredProbe ? [acquiredProbe.exited] : []),
]);
}
}, 15_000);
}, SPAWN_BUDGET_MS);

test("two processes exclude each other and predecessor release cannot delete a successor lock", async () => {
const f = fixture();
Expand Down Expand Up @@ -291,7 +293,7 @@ describe("native main profile transactions", () => {
await first.exited;
if (second) await second.exited;
}
}, 15_000);
}, SPAWN_BUDGET_MS);

test("the same canonical CODEX_HOME serializes different OpenCodex config roots", async () => {
const f = fixture();
Expand All @@ -318,7 +320,7 @@ describe("native main profile transactions", () => {
writeFileSync(release, "release");
await first.exited;
}
}, 15_000);
}, SPAWN_BUDGET_MS);

test("shares one vault while preventing another OPENCODEX_HOME from finishing or cancelling a stage", async () => {
const f = fixture();
Expand Down
9 changes: 6 additions & 3 deletions tests/codex-integration/native-profile-startup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
import { startServer } from "../../src/server";
import { removeTreeWithRetry } from "../helpers/remove-tree";
import { helperPath, repoRoot } from "../helpers/repo-root";
import { INTERNAL_DEADLINE_MS } from "../helpers/test-budget";

const roots: string[] = [];
const previousOpencodexHome = process.env.OPENCODEX_HOME;
Expand Down Expand Up @@ -226,7 +227,8 @@ async function fixture(
return { root, codexHome, configDir, key, manager, target, sourceProfileId: sourceRecord.id, targetProfileId: targetRecord.id };
}

async function waitForPath(path: string, timeoutMs = 10_000): Promise<void> {
// Gates on a spawned child reaching its marker: 8-19 s on windows-latest (run 33930757649).
async function waitForPath(path: string, timeoutMs = INTERNAL_DEADLINE_MS): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (!existsSync(path) && Date.now() < deadline) await Bun.sleep(10);
if (!existsSync(path)) throw new Error(`Timed out waiting for ${path}`);
Expand All @@ -239,8 +241,9 @@ async function waitForPath(path: string, timeoutMs = 10_000): Promise<void> {
* Wait for a port that is actually a port.
*/
// A spawned proxy child needs 10-18 s to reach its port file on a loaded windows-latest shard
// (runs 33601508392 and 33610501053); every caller here has a 20 s+ budget.
async function waitForPort(path: string, timeoutMs = 18_000): Promise<number> {
// (runs 33601508392 and 33610501053), and run 33930757649 showed 19 s boots elsewhere in the
// suite. INTERNAL_DEADLINE_MS is the named in-test bound; callers carry a larger case budget.
async function waitForPort(path: string, timeoutMs = INTERNAL_DEADLINE_MS): Promise<number> {
const deadline = Date.now() + timeoutMs;
for (;;) {
if (existsSync(path)) {
Expand Down
5 changes: 4 additions & 1 deletion tests/helpers/storage-policy-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { stopStorageCleanupScheduler } from "../../src/storage/policy-scheduler";
import { drainStorageWorkers } from "../../src/storage/worker-lifecycle";
import { removeTreeWithRetry } from "./remove-tree";
import { INTERNAL_DEADLINE_MS } from "./test-budget";

export function baseConfig(): OcxConfig {
return {
Expand Down Expand Up @@ -58,7 +59,9 @@ export function seedArchived(codexHome: string): void {
export async function waitForJobIdle(
serverUrl: URL,
startedAt: number,
timeoutMs = 15_000,
// Polls a live server for a worker-backed job to settle; the worker's OS-thread join is
// the slow half on Windows. Named so every caller inherits the same bound.
timeoutMs = INTERNAL_DEADLINE_MS,
): Promise<{
enabled: boolean;
lastRun?: { removed: number };
Expand Down
Loading
Loading