mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 00:03:47 +00:00
test(skills): exercise real Windows rename contention
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { renameSkillPathWithWindowsRetry } from './skill-filesystem-retry'
|
||||
|
||||
const RUN_REAL_WINDOWS =
|
||||
process.platform === 'win32' && process.env.ORCA_REAL_WINDOWS_SKILL_TEST === '1'
|
||||
const roots: string[] = []
|
||||
const lockers: ChildProcessWithoutNullStreams[] = []
|
||||
|
||||
const LOCK_SCRIPT = [
|
||||
'$stream = [System.IO.File]::Open($args[0], [System.IO.FileMode]::Open,',
|
||||
' [System.IO.FileAccess]::Read, [System.IO.FileShare]::None)',
|
||||
"[Console]::Out.WriteLine('LOCKED')",
|
||||
'Start-Sleep -Milliseconds ([int]$args[1])',
|
||||
'$stream.Dispose()'
|
||||
].join(' ')
|
||||
|
||||
function holdFile(path: string, durationMs: number) {
|
||||
const child = spawn(
|
||||
'powershell.exe',
|
||||
['-NoProfile', '-NonInteractive', '-Command', LOCK_SCRIPT, path, String(durationMs)],
|
||||
{ windowsHide: true }
|
||||
)
|
||||
lockers.push(child)
|
||||
let ready = false
|
||||
const locked = new Promise<void>((resolve, reject) => {
|
||||
child.once('error', reject)
|
||||
child.stdout.on('data', (chunk: Buffer) => {
|
||||
if (chunk.toString().includes('LOCKED')) {
|
||||
ready = true
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
child.once('exit', (code) => {
|
||||
if (!ready) {
|
||||
reject(new Error(`windows-locker-exited-${code ?? 'unknown'}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
const released = new Promise<void>((resolve, reject) => {
|
||||
child.once('error', reject)
|
||||
child.once('exit', (code) => {
|
||||
if (code === 0) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`windows-locker-exited-${code ?? 'unknown'}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
return { locked, released }
|
||||
}
|
||||
|
||||
async function fixture(label: string) {
|
||||
const root = await mkdtemp(join(tmpdir(), `orca-windows-rename-${label}-`))
|
||||
roots.push(root)
|
||||
const source = join(root, 'source')
|
||||
const target = join(root, 'target')
|
||||
const file = join(source, 'SKILL.md')
|
||||
await mkdir(source)
|
||||
await writeFile(file, 'private skill')
|
||||
return { source, target, file }
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
for (const child of lockers.splice(0)) {
|
||||
if (child.exitCode === null) {
|
||||
child.kill()
|
||||
}
|
||||
}
|
||||
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
|
||||
})
|
||||
|
||||
describe.runIf(RUN_REAL_WINDOWS)('real Windows rename contention', () => {
|
||||
it('retries a transient exclusive file lock', async () => {
|
||||
const value = await fixture('transient')
|
||||
const lock = holdFile(value.file, 300)
|
||||
await lock.locked
|
||||
|
||||
await renameSkillPathWithWindowsRetry(value.source, value.target)
|
||||
await lock.released
|
||||
|
||||
expect(await readFile(join(value.target, 'SKILL.md'), 'utf8')).toBe('private skill')
|
||||
})
|
||||
|
||||
it('preserves the source after bounded retry exhaustion', async () => {
|
||||
const value = await fixture('exhaustion')
|
||||
const lock = holdFile(value.file, 2_000)
|
||||
await lock.locked
|
||||
|
||||
await expect(renameSkillPathWithWindowsRetry(value.source, value.target)).rejects.toMatchObject(
|
||||
{
|
||||
code: expect.stringMatching(/^(EACCES|EBUSY|EPERM)$/)
|
||||
}
|
||||
)
|
||||
expect(await readFile(value.file, 'utf8')).toBe('private skill')
|
||||
await lock.released
|
||||
|
||||
await expect(
|
||||
renameSkillPathWithWindowsRetry(value.source, value.target)
|
||||
).resolves.toBeUndefined()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user