Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* backend-release-gate.windows-live.test.ts
|
||||
*
|
||||
* LIVE Windows E2E for the #74805 unlock gate: real spawned processes, the
|
||||
* REAL isPidAliveWindows probe against the live process table, real
|
||||
* taskkill — no fake clocks, no fake tables. Runs only on win32 (the
|
||||
* ephemeral wine2e lane); skipped everywhere else.
|
||||
*
|
||||
* This is the platform half of the proof: the unit suite pins the gate's
|
||||
* decision logic on a fake table; this file proves the two real-world
|
||||
* premises the fix rests on:
|
||||
* 1. taskkill /T /F returns while the killed process is still enumerable
|
||||
* (the race window exists), and
|
||||
* 2. the gate, wired to the real probes, dwells through that window and
|
||||
* only passes once the PID has genuinely left the table.
|
||||
*/
|
||||
|
||||
import { execFileSync, spawn } from 'node:child_process'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { isPidAliveWindows, waitForBackendRelease } from './backend-release-gate'
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
function spawnSleeper(): { pid: number; kill: () => void } {
|
||||
// A real python if available (mirrors the backend shape), else powershell.
|
||||
const child = spawn('powershell', ['-NoProfile', '-Command', 'Start-Sleep -Seconds 300'], { stdio: 'ignore' })
|
||||
|
||||
if (!child.pid) {
|
||||
throw new Error('sleeper failed to spawn')
|
||||
}
|
||||
|
||||
return {
|
||||
pid: child.pid,
|
||||
kill: () => {
|
||||
try {
|
||||
child.kill()
|
||||
} catch {
|
||||
/* already gone */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function taskkillTree(pid: number): void {
|
||||
try {
|
||||
execFileSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' })
|
||||
} catch {
|
||||
/* already gone */
|
||||
}
|
||||
}
|
||||
|
||||
describe.skipIf(!isWindows)('waitForBackendRelease — live Windows (#74805)', () => {
|
||||
it('isPidAliveWindows tracks a real process through spawn and exit', async () => {
|
||||
const sleeper = spawnSleeper()
|
||||
|
||||
expect(isPidAliveWindows(sleeper.pid)).toBe(true)
|
||||
|
||||
taskkillTree(sleeper.pid)
|
||||
|
||||
// Poll until the table retires the PID (bounded).
|
||||
const deadline = Date.now() + 10000
|
||||
|
||||
while (isPidAliveWindows(sleeper.pid) && Date.now() < deadline) {
|
||||
await new Promise(r => setTimeout(r, 100))
|
||||
}
|
||||
|
||||
expect(isPidAliveWindows(sleeper.pid)).toBe(false)
|
||||
})
|
||||
|
||||
it('the gate dwells until a real killed PID leaves the live process table', async () => {
|
||||
const sleeper = spawnSleeper()
|
||||
const logs: string[] = []
|
||||
let firstAliveCheck: boolean | null = null
|
||||
|
||||
// Fire the real taskkill and IMMEDIATELY enter the gate — the #74805
|
||||
// shape. The shim probe reads unlocked throughout (the serve backend
|
||||
// never held it); only the PID exit-wait can hold the gate closed.
|
||||
taskkillTree(sleeper.pid)
|
||||
|
||||
const result = await waitForBackendRelease(
|
||||
[sleeper.pid],
|
||||
{
|
||||
isShimLocked: () => false,
|
||||
isPidAlive: pid => {
|
||||
const alive = isPidAliveWindows(pid)
|
||||
|
||||
if (firstAliveCheck === null) {
|
||||
firstAliveCheck = alive
|
||||
}
|
||||
|
||||
return alive
|
||||
},
|
||||
collectStragglerPids: () => [],
|
||||
killProcessTree: taskkillTree,
|
||||
sleep: ms => new Promise(r => setTimeout(r, ms)),
|
||||
now: () => Date.now(),
|
||||
log: line => logs.push(line)
|
||||
},
|
||||
'live-e2e'
|
||||
)
|
||||
|
||||
expect(result.unlocked).toBe(true)
|
||||
// The gate resolved only after the real PID left the real table:
|
||||
expect(isPidAliveWindows(sleeper.pid)).toBe(false)
|
||||
expect(result.lingeringPids).toEqual([])
|
||||
// Record whether the race window was observable on this runner (taskkill
|
||||
// returned while the PID was still enumerable). Informational: fast
|
||||
// runners can retire tiny process trees before our first check, but the
|
||||
// gate's correctness (above) does not depend on winning that race.
|
||||
logs.push(`race-window-observed=${firstAliveCheck}`)
|
||||
|
||||
expect(logs.some(l => l.includes('safe to proceed'))).toBe(true)
|
||||
})
|
||||
|
||||
it('a live foreign holder keeps the gate closed until the deadline', async () => {
|
||||
const holder = spawnSleeper()
|
||||
|
||||
try {
|
||||
const result = await waitForBackendRelease(
|
||||
[holder.pid],
|
||||
{
|
||||
// Simulates the shim held by a process we did NOT kill — the gate
|
||||
// must fail closed rather than hand off over a live holder.
|
||||
isShimLocked: () => true,
|
||||
isPidAlive: isPidAliveWindows,
|
||||
collectStragglerPids: () => [],
|
||||
killProcessTree: () => {
|
||||
/* nothing else to kill */
|
||||
},
|
||||
sleep: ms => new Promise(r => setTimeout(r, ms)),
|
||||
now: () => Date.now(),
|
||||
log: () => {}
|
||||
},
|
||||
'live-e2e',
|
||||
2000
|
||||
)
|
||||
|
||||
expect(result.unlocked).toBe(false)
|
||||
expect(result.lingeringPids).toEqual([holder.pid])
|
||||
} finally {
|
||||
taskkillTree(holder.pid)
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user