mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
159 lines
5.0 KiB
TypeScript
159 lines
5.0 KiB
TypeScript
import { join } from 'node:path'
|
|
import { homedir } from 'node:os'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
resolveCodexHomeProcessLockKey,
|
|
resolveCodexHomeProcessLockKeyForSpawnEnv,
|
|
withCodexHomeProcessLock
|
|
} from './codex-home-process-lock'
|
|
|
|
function deferred<T = void>() {
|
|
let resolve!: (value: T) => void
|
|
let reject!: (error: unknown) => void
|
|
const promise = new Promise<T>((res, rej) => {
|
|
resolve = res
|
|
reject = rej
|
|
})
|
|
return { promise, resolve, reject }
|
|
}
|
|
|
|
describe('withCodexHomeProcessLock', () => {
|
|
it('serializes runs that share a lock key', async () => {
|
|
const events: string[] = []
|
|
const firstGate = deferred()
|
|
|
|
const first = withCodexHomeProcessLock('home-a', async () => {
|
|
events.push('first:start')
|
|
await firstGate.promise
|
|
events.push('first:end')
|
|
return 1
|
|
})
|
|
const second = withCodexHomeProcessLock('home-a', async () => {
|
|
events.push('second:start')
|
|
return 2
|
|
})
|
|
|
|
await Promise.resolve()
|
|
expect(events).toEqual(['first:start'])
|
|
|
|
firstGate.resolve()
|
|
await expect(first).resolves.toBe(1)
|
|
await expect(second).resolves.toBe(2)
|
|
expect(events).toEqual(['first:start', 'first:end', 'second:start'])
|
|
})
|
|
|
|
it('runs different lock keys concurrently', async () => {
|
|
const events: string[] = []
|
|
const firstGate = deferred()
|
|
|
|
const first = withCodexHomeProcessLock('home-a', async () => {
|
|
events.push('a:start')
|
|
await firstGate.promise
|
|
})
|
|
const second = withCodexHomeProcessLock('home-b', async () => {
|
|
events.push('b:start')
|
|
})
|
|
|
|
await second
|
|
expect(events).toEqual(['a:start', 'b:start'])
|
|
firstGate.resolve()
|
|
await first
|
|
})
|
|
|
|
it('releases the lock after a rejected run', async () => {
|
|
await expect(
|
|
withCodexHomeProcessLock('home-a', async () => {
|
|
throw new Error('boom')
|
|
})
|
|
).rejects.toThrow('boom')
|
|
|
|
await expect(withCodexHomeProcessLock('home-a', async () => 'after')).resolves.toBe('after')
|
|
})
|
|
|
|
it('does not release a running lock based on elapsed time', async () => {
|
|
vi.useFakeTimers()
|
|
try {
|
|
const events: string[] = []
|
|
const gate = deferred()
|
|
const first = withCodexHomeProcessLock('home-long-running', async () => {
|
|
events.push('first:start')
|
|
await gate.promise
|
|
})
|
|
const second = withCodexHomeProcessLock('home-long-running', async () => {
|
|
events.push('second:start')
|
|
})
|
|
|
|
await vi.advanceTimersByTimeAsync(60 * 60_000)
|
|
expect(events).toEqual(['first:start'])
|
|
|
|
gate.resolve()
|
|
await Promise.all([first, second])
|
|
expect(events).toEqual(['first:start', 'second:start'])
|
|
} finally {
|
|
vi.useRealTimers()
|
|
}
|
|
})
|
|
|
|
it('keys explicit and default host homes consistently', () => {
|
|
const previousCodexHome = process.env.CODEX_HOME
|
|
delete process.env.CODEX_HOME
|
|
try {
|
|
const defaultKey = resolveCodexHomeProcessLockKey(null)
|
|
expect(resolveCodexHomeProcessLockKey(join(homedir(), '.codex'))).toBe(defaultKey)
|
|
expect(resolveCodexHomeProcessLockKeyForSpawnEnv(undefined)).toBe(defaultKey)
|
|
expect(resolveCodexHomeProcessLockKey('/somewhere/else')).not.toBe(defaultKey)
|
|
} finally {
|
|
if (previousCodexHome !== undefined) {
|
|
process.env.CODEX_HOME = previousCodexHome
|
|
}
|
|
}
|
|
})
|
|
|
|
it('keys a stripped child env to the real default home, not ambient CODEX_HOME', () => {
|
|
const previousCodexHome = process.env.CODEX_HOME
|
|
process.env.CODEX_HOME = '/nested-orca/managed-home'
|
|
try {
|
|
expect(resolveCodexHomeProcessLockKeyForSpawnEnv({ PATH: process.env.PATH })).toBe(
|
|
resolveCodexHomeProcessLockKey(join(homedir(), '.codex'))
|
|
)
|
|
expect(resolveCodexHomeProcessLockKeyForSpawnEnv(undefined)).toBe(
|
|
resolveCodexHomeProcessLockKey('/nested-orca/managed-home')
|
|
)
|
|
} finally {
|
|
if (previousCodexHome === undefined) {
|
|
delete process.env.CODEX_HOME
|
|
} else {
|
|
process.env.CODEX_HOME = previousCodexHome
|
|
}
|
|
}
|
|
})
|
|
|
|
it('keys a WSL UNC probe home and a WSL spawn env to the same lock', () => {
|
|
const probeKey = resolveCodexHomeProcessLockKey('\\\\wsl$\\Ubuntu\\home\\user\\.codex')
|
|
const spawnKey = resolveCodexHomeProcessLockKeyForSpawnEnv(
|
|
{ CODEX_HOME: '/home/user/.codex' },
|
|
'Ubuntu'
|
|
)
|
|
expect(spawnKey).toBe(probeKey)
|
|
})
|
|
|
|
it('uses the WSL default sentinel when launcher filtering strips an ambient home', () => {
|
|
const previousCodexHome = process.env.CODEX_HOME
|
|
process.env.CODEX_HOME = '/host-only/codex-home'
|
|
try {
|
|
const inheritedKey = resolveCodexHomeProcessLockKeyForSpawnEnv(
|
|
{ CODEX_HOME: '/host-only/codex-home' },
|
|
'Ubuntu'
|
|
)
|
|
const strippedKey = resolveCodexHomeProcessLockKeyForSpawnEnv({}, 'Ubuntu')
|
|
expect(inheritedKey).toBe(strippedKey)
|
|
} finally {
|
|
if (previousCodexHome === undefined) {
|
|
delete process.env.CODEX_HOME
|
|
} else {
|
|
process.env.CODEX_HOME = previousCodexHome
|
|
}
|
|
}
|
|
})
|
|
})
|