96 lines
4.0 KiB
TypeScript
96 lines
4.0 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { assertRuntimeAvailable, pauseRuntimeStarts, resumeRuntimeStarts, withRuntimeStart } from '../src/main/update-maintenance'
|
|
import { createUpdateLifecycle } from '../src/main/update-lifecycle'
|
|
|
|
let release!: () => void
|
|
const running = withRuntimeStart(() => new Promise<void>(resolve => { release = resolve }))
|
|
await Promise.resolve()
|
|
let paused = false
|
|
const pendingPause = pauseRuntimeStarts().then(() => { paused = true })
|
|
assert.throws(assertRuntimeAvailable, /准备重启/)
|
|
assert.throws(() => withRuntimeStart(async () => undefined), /准备重启/)
|
|
await Promise.resolve()
|
|
assert.equal(paused, false)
|
|
release()
|
|
await running
|
|
await pendingPause
|
|
assert.equal(paused, true)
|
|
resumeRuntimeStarts()
|
|
assert.doesNotThrow(assertRuntimeAvailable)
|
|
await assert.rejects(withRuntimeStart(async () => { throw new Error('failed start') }), /failed start/)
|
|
await pauseRuntimeStarts()
|
|
resumeRuntimeStarts()
|
|
assert.equal(await withRuntimeStart(async () => 42), 42)
|
|
console.log('PASS update maintenance: in-flight start drain, new work rejection, resume, rejected-start cleanup')
|
|
|
|
let resumed = 0
|
|
let finishDirectQuiesce!: () => void
|
|
let directQuiesceStarted!: () => void
|
|
const directQuiesceStart = new Promise<void>(resolve => { directQuiesceStarted = resolve })
|
|
let directQuiesceCalls = 0
|
|
const directLifecycle = createUpdateLifecycle({
|
|
quiesce: () => {
|
|
directQuiesceCalls += 1
|
|
directQuiesceStarted()
|
|
return new Promise<void>(resolve => { finishDirectQuiesce = resolve })
|
|
},
|
|
restore: () => { throw new Error('successful quiesce must not restore') },
|
|
shutdown: async () => undefined,
|
|
exit: () => undefined,
|
|
log: () => undefined
|
|
})
|
|
const directInstall = directLifecycle.beforeInstall()
|
|
await directQuiesceStart
|
|
assert.equal(directQuiesceCalls, 1, 'install starts quiesce without a confirmation port')
|
|
let directResolved = false
|
|
void directInstall.then(() => { directResolved = true })
|
|
await Promise.resolve()
|
|
assert.equal(directResolved, false, 'beforeInstall waits for quiesce completion')
|
|
await assert.rejects(directLifecycle.beforeInstall(), /上次更新准备仍在结束中/)
|
|
assert.equal(directQuiesceCalls, 1, 'concurrent beforeInstall must not start quiesce twice')
|
|
finishDirectQuiesce()
|
|
assert.equal(await directInstall, true)
|
|
|
|
let finishQuiesce!: () => void
|
|
const deferredLifecycle = createUpdateLifecycle({
|
|
quiesce: () => new Promise<void>(resolve => { finishQuiesce = resolve }),
|
|
restore: () => { resumed += 1 },
|
|
shutdown: async () => undefined,
|
|
exit: () => undefined,
|
|
log: () => undefined,
|
|
prepareTimeoutMs: 2
|
|
})
|
|
await assert.rejects(deferredLifecycle.beforeInstall(), /清理尚未结束/)
|
|
deferredLifecycle.installFailed()
|
|
assert.equal(resumed, 0, 'timeout must not resume while old quiesce is still running')
|
|
finishQuiesce()
|
|
await new Promise(resolve => setTimeout(resolve, 0))
|
|
assert.equal(resumed, 1, 'runtime resumes only after timed-out quiesce settles')
|
|
|
|
let exited = 0
|
|
let restarts = 0
|
|
let shutdowns = 0
|
|
const committedLifecycle = createUpdateLifecycle({
|
|
quiesce: async () => undefined,
|
|
restore: () => { restarts += 1 },
|
|
shutdown: async () => { shutdowns += 1 },
|
|
exit: () => { exited += 1 },
|
|
log: () => undefined
|
|
})
|
|
assert.equal(await committedLifecycle.beforeInstall(), true)
|
|
await committedLifecycle.quitForUpdate()
|
|
committedLifecycle.installFailed()
|
|
assert.deepEqual([shutdowns, exited, restarts], [1, 1, 0], 'committed installer exit must never restore work')
|
|
await assert.rejects(committedLifecycle.beforeInstall(), /安装器已启动/)
|
|
const hungShutdown = createUpdateLifecycle({
|
|
quiesce: async () => undefined,
|
|
restore: () => { throw new Error('must not restore committed installer') },
|
|
shutdown: () => new Promise<void>(() => undefined),
|
|
exit: () => { exited += 1 },
|
|
log: () => undefined,
|
|
quitTimeoutMs: 2
|
|
})
|
|
await hungShutdown.quitForUpdate()
|
|
assert.equal(exited, 2, 'hung shutdown is bounded and still releases the installer PID')
|
|
console.log('PASS update lifecycle: direct quiesce, concurrent barrier, timeout recovery, post-launch no-resume commit')
|