test(desktop): source hand-off test's fake script takes the bridge marker

Contract C2: a script hand-off is confirmed only when the script takes the
Desktop's bridge marker in its own name, not when the launcher exits 0. The
channel hand-off test mocked spawnUpdaterProcess with a script that never
claimed, so apply() waited out the 20 s claim window and correctly reported
updater-spawn-failed. The fake now takes the marker as a live foreign pid,
and the test releases it after each hand-off (the update finished) so the
next apply does not see a live foreign owner. Claim timeout lowered to 2 s.

(cherry picked from commit fb43b97f0f501a2693130b4d8828014a380ae211)
This commit is contained in:
Teknium
2026-10-03 14:31:23 -07:00
parent 3285e0a22c
commit b5ce061f41

View File

@@ -8,6 +8,7 @@ import { promisify } from 'node:util'
import { expect, it, vi } from 'vitest'
import { markerPath } from '../update-marker'
import * as updaterProcess from '../updater-process'
import { type CheckoutStrategyDeps, createCheckoutStrategy } from './checkout'
@@ -366,6 +367,7 @@ urllib.request.build_opener = local_build
isMac: process.platform === 'darwin',
defaultUpdateBranch: 'main',
updateHandoffDwellMs: 0,
handoffClaimTimeoutMs: 2000,
resolveUpdateRoot: (): string => root,
readSourceUpdate: (install: string, opts: { force?: boolean }): Promise<SourceUpdate | null> =>
readSourceUpdate({
@@ -395,6 +397,9 @@ urllib.request.build_opener = local_build
vi.spyOn(updaterProcess, 'spawnUpdaterProcess').mockImplementation(
(command: string, args: string[], options: SpawnOptions): updaterProcess.UpdaterChild => {
spawned.push({ command, args, options })
// C2: a hand-off has started only once the script takes the Desktop's
// bridge marker in its own (live, foreign) name — do that here.
fs.writeFileSync(markerPath(home), `${process.ppid}\n${Math.floor(Date.now() / 1000)}\n`)
return { unref: (): void => {} }
}
@@ -426,6 +431,8 @@ urllib.request.build_opener = local_build
fs.writeFileSync(script, '')
expect(await strategy.apply()).toMatchObject({ ok: true, handedOff: true })
const handoff: (typeof spawned)[number] | undefined = spawned.pop()
// The update ran and released its claim; the next apply starts clean.
fs.rmSync(markerPath(home), { force: true })
expect(handoff?.args).toContain(script)
expect(handoff?.args).toContain(channel)
expect(handoff?.args).toContain(process.platform === 'win32' ? '-Channel' : '--channel')
@@ -468,6 +475,7 @@ urllib.request.build_opener = local_build
expect(spawned.pop()?.args).toEqual(
expect.arrayContaining([process.platform === 'win32' ? '-Branch' : '--branch', 'feature/gui'])
)
fs.rmSync(markerPath(home), { force: true })
fs.rmSync(scriptDirectory, { recursive: true, force: true })
expect(await strategy.apply()).toMatchObject({ manual: true, command: 'hermes update --branch feature/gui' })
// apply() forces a fresh check; under the R2 protocol that re-resolution