From b5ce061f41c550584d3e8a5ca93a4bfda063deaf Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 3 Oct 2026 14:31:23 -0700 Subject: [PATCH] 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) --- apps/desktop/electron/updater/checkout-source.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/desktop/electron/updater/checkout-source.test.ts b/apps/desktop/electron/updater/checkout-source.test.ts index e7e0bc23b4..3d273b3e5b 100644 --- a/apps/desktop/electron/updater/checkout-source.test.ts +++ b/apps/desktop/electron/updater/checkout-source.test.ts @@ -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 => 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