test: target writer timeouts and diagnose Bun startup stalls (#23267)

* test(persistence): start the fault timer after the real worker is ready

* test(orcad): preserve launcher startup phase evidence on timeout

* test: isolate historical hourly build version inputs

* test: align historical package input with shared CI repair

* test: inject hourly package version without module mocking

* test: share the hourly package input contract across CI repairs

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
This commit is contained in:
OrcaWin
2026-09-26 15:05:44 -07:00
committed by GitHub
co-authored by m4air
parent b4717f0f57
commit 10cfe9218f
@@ -1,5 +1,5 @@
import { build } from 'esbuild'
import { existsSync } from 'node:fs'
import { existsSync, readFileSync } from 'node:fs'
import { copyFile, mkdtemp, readFile, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
@@ -23,6 +23,22 @@ describe.skipIf(!existsSync(runtimePath))('real Bun launcher lifecycle', () => {
await copyFile(runtimePath, join(directory, orcadBunRuntimeFilename(process.platform)))
await writeFile(join(directory, '.build-target'), `${process.platform}-${process.arch}\n`)
await build({
// Record entry before imports without relying on either process's stdout.
banner: {
js: `
function traceLauncherPhase(phase) {
const runtime = process.versions.bun ? 'bun' : 'node'
try {
require('node:fs').appendFileSync(process.env.ORCA_TEST_PHASES + '.' + runtime,
JSON.stringify({ at: Date.now(), phase, pid: process.pid, runtime, arch: process.arch }) + '\\n')
} catch (error) {
console.error('launcher fixture trace failed:', error)
}
}
traceLauncherPhase('entry')
process.once('exit', code => traceLauncherPhase('exit:' + code))
`
},
stdin: {
contents: `
import {handoffToBundledOrcad, OrcadBundledRuntimeError} from './src/main/orcad/orcad-bundled-runtime'
@@ -30,12 +46,16 @@ describe.skipIf(!existsSync(runtimePath))('real Bun launcher lifecycle', () => {
import {resolveOrcadExitCode} from './src/main/orcad/orcad-exit-code'
import {writeFile} from 'node:fs/promises'
if (!process.versions.bun) {
traceLauncherPhase('before-handoff')
if (!handoffToBundledOrcad()) throw new Error('Missing bundled runtime')
traceLauncherPhase('after-handoff')
process.on('message', signal => process.emit(signal))
} else {
traceLauncherPhase('before-booting')
console.log('booting:' + process.pid)
console.log('runtime:' + process.versions.bun)
console.log('channel-env:' + (process.env.ORCA_BUNDLED_LAUNCHER_CHANNEL ?? 'absent'))
traceLauncherPhase('booting-written')
process.on('exit', code => console.log('runtime-exit:' + code))
const keepalive = setInterval(() => {}, 1_000)
const install = async () => {
@@ -99,6 +119,13 @@ describe.skipIf(!existsSync(runtimePath))('real Bun launcher lifecycle', () => {
const runtime = options.direct
? join(directory, orcadBunRuntimeFilename(process.platform))
: nodePath
const startedAt = Date.now()
const events: { at: number; event: string }[] = []
const record = (event: string): void => {
if (events.length < 16) {
events.push({ at: Date.now(), event })
}
}
const child = spawnProcess({
program: options.nohup ? 'nohup' : runtime,
args: [...(options.nohup ? [runtime] : []), join(directory, 'orcad.js')],
@@ -106,6 +133,7 @@ describe.skipIf(!existsSync(runtimePath))('real Bun launcher lifecycle', () => {
...process.env,
ORCA_BACKGROUND_LAUNCH: '1',
ORCA_TEST_DONE: join(directory, 'done'),
ORCA_TEST_PHASES: join(directory, 'phases'),
ORCA_TEST_DELAY_INSTALL: options.delay ? '1' : '0',
ORCA_TEST_STALL: options.stall ? '1' : '0',
ORCA_TEST_FAIL_STARTUP: options.failStartup ? '1' : '0'
@@ -114,8 +142,10 @@ describe.skipIf(!existsSync(runtimePath))('real Bun launcher lifecycle', () => {
stdio: ['ignore', 'pipe', 'pipe', 'ipc']
})
children.add(child)
child.once('spawn', () => record('spawn'))
let closed = false
child.once('close', () => {
record('close')
closed = true
})
let output = ''
@@ -128,18 +158,57 @@ describe.skipIf(!existsSync(runtimePath))('real Bun launcher lifecycle', () => {
runtimes.delete(Number(pid))
}
}
child.stdout.on('data', capture)
child.stderr.on('data', capture)
const streams = [
['stdout', child.stdout],
['stderr', child.stderr]
] as const
for (const [name, stream] of streams) {
stream.on('data', capture)
stream.once('data', () => record(`${name}:data`))
stream.once('end', () => record(`${name}:end`))
stream.once('close', () => record(`${name}:close`))
}
const exit = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
(resolve, reject) => {
child.once('error', reject)
child.once('error', (error) => {
record(`error:${error.message}`)
reject(error)
})
child.once('exit', (code, signal) => {
record(`exit:${code}:${signal}`)
children.delete(child)
resolve({ code, signal })
})
}
)
return { child, output: () => output, exit, isClosed: () => closed }
const diagnostics = () => ({
startedAt,
elapsedMs: Date.now() - startedAt,
events,
pid: child.pid,
exitCode: child.exitCode,
signalCode: child.signalCode,
connected: child.connected,
closed,
streams: streams.map(([name, stream]) => ({
name,
ended: stream.readableEnded,
destroyed: stream.destroyed,
error: stream.errored?.message,
bufferedBytes: stream.readableLength
})),
phases: ['node', 'bun'].map((runtime) => {
try {
return {
runtime,
trace: readFileSync(join(directory, `phases.${runtime}`), 'utf8').slice(0, 4096)
}
} catch (error) {
return { runtime, error: String(error) }
}
})
})
return { child, output: () => output, exit, isClosed: () => closed, diagnostics }
}
it.each([false, true])(
@@ -214,7 +283,12 @@ describe.skipIf(!existsSync(runtimePath))('real Bun launcher lifecycle', () => {
it('preserves a startup configuration verdict after early launcher loss', async () => {
const h = launch({ delay: true, failStartup: true })
await vi.waitFor(() => expect(h.output()).toContain('booting:'), { timeout: 5_000 })
try {
await vi.waitFor(() => expect(h.output()).toContain('booting:'), { timeout: 5_000 })
} catch (error) {
console.error('Bun launcher startup diagnostics:', JSON.stringify(h.diagnostics()))
throw error
}
h.child.kill('SIGKILL')
await h.exit
await vi.waitFor(() => expect(h.output()).toContain('runtime-exit:78'), { timeout: 5_000 })