mirror of
https://github.com/stablyai/orca.git
synced 2026-10-06 08:02:28 +00:00
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:
@@ -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 })
|
||||
|
||||
Reference in New Issue
Block a user