Files
hapi/cli/src/codex/shared/registry.test.ts
T
AnanovoandGitHub 2a612c5c72 perf(cli): reduce Windows process probes during session shutdown (#2046)
* fix(runner): prevent Windows process probes from blocking session lifecycle

* perf(cli): reduce Windows process probes during session shutdown

* test(cli): update PowerShell fixtures for complete process snapshots
2026-10-28 12:11:40 +08:00

78 lines
5.6 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
const state = vi.hoisted(() => ({ home: '', auth: 'token', processes: new Map<number, string | undefined>() }));
vi.mock('@/configuration', () => ({ configuration: { get happyHomeDir() { return state.home; }, apiUrl: 'hub', get cliApiToken() { return state.auth; } } }));
vi.mock('@/utils/process', () => ({ isProcessAlive: (pid: number) => state.processes.has(pid), getProcessStartMarker: (pid: number) => state.processes.get(pid) }));
import { findRuntime, readRuntimes, runtimeAlive, runtimeAuthHash, runtimeMayBeAlive, saveRuntime, withThreadOwnership, type CodexRuntimeRecord } from './registry';
const directories: string[] = [];
afterEach(async () => { state.processes.clear(); state.auth = 'token'; await Promise.all(directories.splice(0).map(path => rm(path, { recursive: true, force: true }))); });
async function fixture(): Promise<CodexRuntimeRecord> {
const home = await mkdtemp(join(tmpdir(), 'hapi-owner-')); directories.push(home); state.home = join(home, 'hapi');
return { id: 'owner', pid: 1111, marker: 'worker-start', serverPid: 2222, serverMarker: 'server-start', command: 'codex', args: [],
codexHome: join(home, 'codex'), endpoint: 'unix://private', hub: 'hub', authHash: runtimeAuthHash(),
sessions: { sid: { threadId: 'thread', namespace: 'ns', active: true } } };
}
describe('shared runtime ownership', () => {
it('uses one complete process snapshot without per-record OS probes', async () => {
const owner = await fixture();
const probe = vi.spyOn(state.processes, 'has');
try {
for (let scan = 0; scan < 3; scan++) {
for (let index = 0; index < 50; index++) {
expect(runtimeMayBeAlive({ ...owner, pid: 3000 + index, serverPid: 4000 + index }, new Map())).toBe(false);
}
}
expect(probe).not.toHaveBeenCalled();
} finally { probe.mockRestore(); }
});
it('retains live wrappers, orphan engines and unknown generations in a fresh snapshot', async () => {
const owner = await fixture();
expect(runtimeMayBeAlive(owner, new Map([[1111, owner.marker]]))).toBe(true);
expect(runtimeMayBeAlive(owner, new Map([[2222, owner.serverMarker!]]))).toBe(true);
expect(runtimeMayBeAlive(owner, new Map([[1111, null]]))).toBe(true);
expect(runtimeMayBeAlive({ ...owner, serverMarker: undefined }, new Map([[2222, 'unverified-start']]))).toBe(true);
});
it('does not confuse old runtime generations with reused PIDs or retain a stale live snapshot', async () => {
const owner = await fixture(); state.processes.set(1111, owner.marker);
expect(runtimeMayBeAlive(owner, new Map([[1111, 'new-wrapper'], [2222, 'new-engine']]))).toBe(false);
expect(runtimeMayBeAlive(owner, new Map())).toBe(false);
expect(runtimeMayBeAlive(owner, new Map([[1111, owner.marker]]))).toBe(true);
});
it('distinguishes PID reuse from an unknown generation and a live orphan engine', async () => {
const owner = await fixture(); state.processes.set(1111, 'different-start');
expect(runtimeAlive(owner)).toBe(false); expect(runtimeMayBeAlive(owner)).toBe(false);
state.processes.set(1111, undefined); expect(runtimeMayBeAlive(owner)).toBe(true);
state.processes.delete(1111); state.processes.set(2222, 'server-start'); expect(runtimeMayBeAlive(owner)).toBe(true);
await saveRuntime(owner);
await expect(withThreadOwnership(owner.codexHome, 'thread', 'new', async () => {})).rejects.toThrow('orphaned');
});
it('protects one native store even across HAPI homes and scopes attach by authentication', async () => {
const owner = await fixture(); state.processes.set(1111, owner.marker); await saveRuntime(owner);
expect(await findRuntime('sid')).toMatchObject({ id: owner.id });
state.auth = 'other'; expect(await findRuntime('sid')).toBeUndefined();
state.home = join(directories[0], 'different-hapi');
await expect(withThreadOwnership(owner.codexHome, 'thread', 'new', async () => {})).rejects.toThrow('hapi resume sid');
state.processes.clear(); expect(await withThreadOwnership(owner.codexHome, 'thread', 'new', async () => 'safe')).toBe('safe');
});
it('fails closed on corrupt records and unknown thread IDs after a creation timeout', async () => {
const owner = await fixture(); owner.pendingCreations = ['unbound']; state.processes.set(1111, owner.marker); await saveRuntime(owner);
await expect(withThreadOwnership(owner.codexHome, 'unknown-thread', 'new', async () => {})).rejects.toThrow('unconfirmed creation');
const directory = join(owner.codexHome, 'hapi-runtime-owners'); await mkdir(directory, { recursive: true });
await writeFile(join(directory, 'broken.json'), '{');
await expect(withThreadOwnership(owner.codexHome, 'thread', 'new', async () => {})).rejects.toThrow('Cannot verify');
});
it('readRuntimes({ strict: true }) fails closed on corrupt hub registry files', async () => {
// Soft [] would let stopSession argv-sweep tree-kill shared wrappers (#1911).
const owner = await fixture();
const directory = join(state.home, 'codex-runtimes');
await mkdir(directory, { recursive: true });
await writeFile(join(directory, 'broken.json'), '{');
expect(await readRuntimes()).toEqual([]);
await expect(readRuntimes({ strict: true })).rejects.toThrow('Cannot verify');
});
});