From 56c7642aae2ca32a23923aeff31f112f4378afdc Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:28:05 -0400 Subject: [PATCH] test(orcad): skip the live-terminal runtime hand-over across a protocol bump (#24429) * test(orcad): skip the Bun-to-Node live-terminal hand-over across a protocol bump The last Bun orcad's daemon reports protocol 38 forever, so asserting the adopted daemon matches this checkout's PROTOCOL_VERSION failed every bump. Ask the Bun slot's daemon for its protocol once, run the hand-over when it matches, and skip with the two versions named when it does not: a daemon at another protocol is never adopted across an update. * test(orcad): clean up the Bun protocol probe even when its launch fails The probe's cleanup ran only after a successful launch, so a launch that timed out or threw left its orcad and daemon running. One finally now stops the orcad, kills what it launched, and kills any daemon named by a pid file in the probe's data root. --- .github/workflows/node-server-tests.yml | 4 +- ...untime-daemon-adoption.integration.test.ts | 62 +++++++++++++++---- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/.github/workflows/node-server-tests.yml b/.github/workflows/node-server-tests.yml index c7cb51e5e67..377ea26174f 100644 --- a/.github/workflows/node-server-tests.yml +++ b/.github/workflows/node-server-tests.yml @@ -125,8 +125,8 @@ jobs: pnpm build:orcad-prebuilds --smoke - run: pnpm build:orcad # Design D7 upgrade and rollback: the last Bun orcad, built from a main commit that shipped - # it at this PROTOCOL_VERSION, beside this checkout's Node slot. Same lockfile, so its build - # reuses this checkout's node_modules. + # it, beside this checkout's Node slot; the live-terminal hand-over skips once PROTOCOL_VERSION + # moves past the Bun daemon's. Same lockfile, so its build reuses this checkout's node_modules. - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 if: runner.os == 'Linux' with: diff --git a/src/main/orcad/orcad-cross-runtime-daemon-adoption.integration.test.ts b/src/main/orcad/orcad-cross-runtime-daemon-adoption.integration.test.ts index 9cb238850dc..09cc44ed3b0 100644 --- a/src/main/orcad/orcad-cross-runtime-daemon-adoption.integration.test.ts +++ b/src/main/orcad/orcad-cross-runtime-daemon-adoption.integration.test.ts @@ -29,6 +29,7 @@ import { orcadNodeRuntimeRelativePath } from '../../shared/orcad-artifacts' import { removeTreeSync } from '../../shared/windows-transient-lock-removal' +import { readDaemonPidRecord } from '../daemon/daemon-endpoint-incarnation' import { PROTOCOL_VERSION } from '../daemon/types' import type { ServeReadiness } from '../server/serve-readiness' import { @@ -91,6 +92,7 @@ const missing = [ let root = '' let client = '' let backupDriver = '' +let bunProtocolVersion = 0 const launched = new Set() /** Without Vitest's markers: daemon-entry.js does not start its server under VITEST. */ @@ -254,6 +256,40 @@ async function backUpProfile(slot: Slot, runtime: string, userDataDir: string): expect(JSON.parse(result.stdout.trim().split('\n').at(-1) ?? '')).toEqual({ ok: true }) } +function killLaunched(): void { + for (const pid of launched) { + try { + process.kill(pid, 'SIGKILL') + } catch {} + } + launched.clear() +} + +/** The protocol the Bun slot's own daemon reports, read from a throwaway launch. */ +async function probeBunDaemonProtocol(): Promise { + const probeRoot = mkdtempSync(join(root, 'bun-probe-')) + const slot = installSlot(probeRoot, 'Bun', bunSlotSource!) + const userDataDir = join(probeRoot, 'data') + try { + const daemon = (await launch(slot, userDataDir)).health!.terminalDaemon + expect(daemon).toMatchObject({ state: 'live', protocolVersion: expect.any(Number) }) + return daemon.protocolVersion! + } finally { + await stop(slot).catch(() => {}) + killLaunched() + // Even after a failed launch: the daemon outlives orcad, and only its pid file names it. + const daemonDir = join(userDataDir, 'daemon') + for (const name of existsSync(daemonDir) ? readdirSync(daemonDir) : []) { + const pid = /^daemon-v\d+\.pid$/.test(name) + ? readDaemonPidRecord(join(daemonDir, name))?.pid + : undefined + if (pid && isAlive(pid)) { + process.kill(pid, 'SIGKILL') + } + } + } +} + // Windows has no orcad launch path (POSIX-only, orcad-remote-host-support.ts), so no inputs. const skip = skipForMissingInputs('cross-runtime', posix ? missing : []) @@ -321,27 +357,28 @@ worker.on('error', (error) => { console.error(error); process.exitCode = 1 }) outfile: client, logLevel: 'silent' }) - }) + bunProtocolVersion = await probeBunDaemonProtocol() + }, 180_000) - afterEach(() => { - for (const pid of launched) { - try { - process.kill(pid, 'SIGKILL') - } catch {} - } - launched.clear() - }) + afterEach(killLaunched) afterAll(() => { removeTreeSync(root) }) - it.each([ + it.for([ ['Bun', 'Node'], ['Node', 'Bun'] ] as const)( '%s orcad hands its daemon and profile to the %s orcad', - async (from: Runtime, to: Runtime) => { + { timeout: 240_000 }, + async ([from, to], context) => { + // A daemon at another protocol is never adopted across an update, so there is no hand-over. + if (bunProtocolVersion !== PROTOCOL_VERSION) { + context.skip( + `the Bun orcad's daemon speaks protocol ${bunProtocolVersion}, this checkout ${PROTOCOL_VERSION}` + ) + } const caseRoot = mkdtempSync(join(root, `${from}-${to}-`)) const userDataDir = join(caseRoot, 'data') const { slots, nodeRuntime } = installSlots(caseRoot) @@ -426,7 +463,6 @@ worker.on('error', (error) => { console.error(error); process.exitCode = 1 }) process.kill(daemonPid, 'SIGKILL') } } - }, - 240_000 + } ) })