mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 08:01:56 +00:00
* fix(windows): ship the process-table addon to the relocated daemon host The Windows terminal daemon runs from a copy of the app under %LOCALAPPDATA%\Orca\daemon-host\<version>. That copy took node-pty but not @vscode/windows-process-tree, so the daemon's bare require of the addon found nothing and every process-table read (foreground tracking, descendant sweeps) fell back to a powershell.exe Get-CimInstance scan (#16905). - Copy the addon's runtime files (package.json, lib/, the .node binary) into the host; the ~25MB of gyp intermediates beside them are filtered out. - Treat a host missing those files as unmaterialized, so hosts built before this are rebuilt, and skip relocation if the install itself lacks them. - Log the daemon's native/CIM capability at startup and warn once when the process table falls back to CIM. Revives #19525 on current main. * test(windows): locate update-survival loss before relaunch * test(windows): preserve daemon tree before update-survival proof * test(windows): distinguish Electron exit from launcher close timeout * test(windows): verify process exit when inherited pipes delay close * test(windows): trace installer process checks in isolated survival runs * fix(windows): probe process-query capability before installer sweep * fix(windows): match installer probe and process-check profile behavior * fix(windows): use NSIS separators for the process-check include * test(windows): dismiss session-search overlay in survival harness --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
120 lines
4.0 KiB
JavaScript
120 lines
4.0 KiB
JavaScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { closeApp } from './app-driver.mjs'
|
|
|
|
const processes = {
|
|
isPidAlive: vi.fn().mockReturnValue(true),
|
|
killTree: vi.fn()
|
|
}
|
|
const strictCloseOptions = { allowForceKill: false, processes }
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
vi.clearAllMocks()
|
|
processes.isPidAlive.mockReturnValue(true)
|
|
})
|
|
|
|
describe('update-survival shutdown', () => {
|
|
it('keeps the force-kill fallback enabled for best-effort cleanup', async () => {
|
|
const app = {
|
|
evaluate: vi.fn().mockResolvedValue(123),
|
|
process: () => ({ pid: 124, stdio: [] }),
|
|
close: vi.fn().mockRejectedValue(new Error('quit rejected'))
|
|
}
|
|
|
|
await closeApp(app, 45_000, { processes })
|
|
|
|
expect(processes.killTree).toHaveBeenCalledExactlyOnceWith(123)
|
|
})
|
|
|
|
it('fails before installation instead of killing the daemon tree after a rejected close', async () => {
|
|
const failure = new Error('quit rejected')
|
|
const app = {
|
|
evaluate: vi.fn().mockResolvedValue(123),
|
|
process: () => ({ pid: 124, stdio: [] }),
|
|
close: vi.fn().mockRejectedValue(failure)
|
|
}
|
|
|
|
await expect(closeApp(app, 45_000, strictCloseOptions)).rejects.toBe(failure)
|
|
expect(processes.killTree).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('allows normal teardown past ten seconds and still rejects a wedged quit without a tree kill', async () => {
|
|
vi.useFakeTimers()
|
|
const app = {
|
|
evaluate: vi.fn().mockResolvedValue(123),
|
|
process: () => ({ pid: 124, stdio: [] }),
|
|
close: vi.fn(() => new Promise(() => {}))
|
|
}
|
|
const closed = closeApp(app, 45_000, strictCloseOptions)
|
|
const rejected = expect(closed).rejects.toThrow('close timeout')
|
|
|
|
await vi.advanceTimersByTimeAsync(30_000)
|
|
expect(vi.getTimerCount()).toBe(1)
|
|
expect(processes.killTree).not.toHaveBeenCalled()
|
|
await vi.advanceTimersByTimeAsync(15_000)
|
|
await rejected
|
|
expect(processes.killTree).not.toHaveBeenCalled()
|
|
expect(vi.getTimerCount()).toBe(0)
|
|
})
|
|
|
|
it('rejects a resolved close while the authoritative main remains live', async () => {
|
|
const app = {
|
|
evaluate: vi.fn().mockResolvedValue(123),
|
|
process: () => ({ pid: 124, stdio: [] }),
|
|
close: vi.fn().mockResolvedValue(undefined)
|
|
}
|
|
|
|
await expect(closeApp(app, 45_000, strictCloseOptions)).rejects.toThrow(
|
|
'authoritative Electron PID remains live'
|
|
)
|
|
expect(processes.isPidAlive).toHaveBeenCalledWith(123)
|
|
expect(processes.killTree).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('accepts verified main and launcher exit while inherited pipes keep close pending', async () => {
|
|
vi.useFakeTimers()
|
|
processes.isPidAlive.mockReturnValue(false)
|
|
const app = {
|
|
evaluate: vi.fn().mockResolvedValue(123),
|
|
process: () => ({ pid: 124, exitCode: 0, stdio: [{ readableEnded: false }] }),
|
|
close: vi.fn(() => new Promise(() => {}))
|
|
}
|
|
const closed = expect(closeApp(app, 45_000, strictCloseOptions)).resolves.toBeUndefined()
|
|
|
|
await vi.advanceTimersByTimeAsync(45_000)
|
|
await closed
|
|
expect(processes.isPidAlive).toHaveBeenCalledWith(123)
|
|
expect(processes.isPidAlive).toHaveBeenCalledWith(124)
|
|
expect(processes.killTree).not.toHaveBeenCalled()
|
|
expect(vi.getTimerCount()).toBe(0)
|
|
})
|
|
|
|
it.each(['live', 'unverifiable'])(
|
|
'rejects a %s launcher even when the main exited',
|
|
async (state) => {
|
|
vi.useFakeTimers()
|
|
processes.isPidAlive.mockImplementation((pid) => {
|
|
if (pid === 123) {
|
|
return false
|
|
}
|
|
if (state === 'unverifiable') {
|
|
throw new Error('process query failed')
|
|
}
|
|
return true
|
|
})
|
|
const app = {
|
|
evaluate: vi.fn().mockResolvedValue(123),
|
|
process: () => ({ pid: 124, stdio: [] }),
|
|
close: vi.fn(() => new Promise(() => {}))
|
|
}
|
|
const closed = expect(closeApp(app, 45_000, strictCloseOptions)).rejects.toThrow(
|
|
'close timeout'
|
|
)
|
|
|
|
await vi.advanceTimersByTimeAsync(45_000)
|
|
await closed
|
|
expect(processes.killTree).not.toHaveBeenCalled()
|
|
}
|
|
)
|
|
})
|