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(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(resolve => { directQuiesceStarted = resolve }) let directQuiesceCalls = 0 const directLifecycle = createUpdateLifecycle({ quiesce: () => { directQuiesceCalls += 1 directQuiesceStarted() return new Promise(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(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(() => 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')