Skip to content

Commit 74a816b

Browse files
committed
Add runtime smoke checks for compiled CLI
1 parent 3accde8 commit 74a816b

3 files changed

Lines changed: 363 additions & 7 deletions

File tree

cli/scripts/smoke-binary.ts

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@
88
* binary, lets it run for a few seconds, then kills it and asserts the TUI
99
* actually rendered a known boot screen.
1010
*
11-
* The positive check matters more than the negative one: a "did the boot
12-
* screen appear" assertion catches *any* startup failure — known fatals,
11+
* Before the long-running UI check, this also invokes explicit compiled
12+
* runtime smoke flags in the binary. Those cover startup-sensitive assets
13+
* (tree-sitter) and non-UI runtime integrations (network, subprocesses,
14+
* vendored native tools, filesystem IO).
15+
*
16+
* The positive boot check matters more than the negative one: a "did the
17+
* boot screen appear" assertion catches *any* startup failure — known fatals,
1318
* novel error messages, silent crashes, hangs, segfaults that produce no
1419
* output. Negative pattern matches are kept only for clearer diagnostics
1520
* when a known regression recurs.
@@ -81,9 +86,21 @@ const FATAL_PATTERNS = [
8186
// the renderer is up).
8287
const DEFAULT_RUN_SECONDS = 10
8388

84-
function runTreeSitterSmoke(binary: string): Promise<void> {
89+
function runFlagSmoke({
90+
binary,
91+
flag,
92+
label,
93+
okPattern,
94+
timeoutMs,
95+
}: {
96+
binary: string
97+
flag: string
98+
label: string
99+
okPattern: RegExp
100+
timeoutMs: number
101+
}): Promise<void> {
85102
return new Promise((resolve, reject) => {
86-
const proc = spawn(binary, ['--smoke-tree-sitter'], {
103+
const proc = spawn(binary, [flag], {
87104
stdio: ['ignore', 'pipe', 'pipe'],
88105
env: { ...process.env, NO_COLOR: '1', TERM: 'dumb' },
89106
})
@@ -95,16 +112,36 @@ function runTreeSitterSmoke(binary: string): Promise<void> {
95112
proc.stdout?.on('data', append)
96113
proc.stderr?.on('data', append)
97114

115+
let timedOut = false
116+
const timeout = setTimeout(() => {
117+
timedOut = true
118+
proc.kill('SIGKILL')
119+
}, timeoutMs)
120+
98121
proc.once('error', reject)
99122
proc.once('exit', (code) => {
100-
if (code === 0 && /tree-sitter smoke ok/.test(captured)) {
123+
clearTimeout(timeout)
124+
125+
if (timedOut) {
126+
reject(
127+
new Error(
128+
`${label} smoke timed out after ${timeoutMs}ms\n${captured.slice(
129+
0,
130+
8 * 1024,
131+
)}`,
132+
),
133+
)
134+
return
135+
}
136+
137+
if (code === 0 && okPattern.test(captured)) {
101138
resolve()
102139
return
103140
}
104141

105142
reject(
106143
new Error(
107-
`tree-sitter smoke failed with exit code ${code}\n${captured.slice(
144+
`${label} smoke failed with exit code ${code}\n${captured.slice(
108145
0,
109146
8 * 1024,
110147
)}`,
@@ -133,9 +170,24 @@ async function main(): Promise<void> {
133170

134171
console.log(`smoke-binary: spawning ${binary} for ${runSeconds}s…`)
135172

136-
await runTreeSitterSmoke(binary)
173+
await runFlagSmoke({
174+
binary,
175+
flag: '--smoke-tree-sitter',
176+
label: 'tree-sitter',
177+
okPattern: /tree-sitter smoke ok/,
178+
timeoutMs: 30_000,
179+
})
137180
console.log('smoke-binary: tree-sitter init OK.')
138181

182+
await runFlagSmoke({
183+
binary,
184+
flag: '--smoke-runtime-primitives',
185+
label: 'runtime primitives',
186+
okPattern: /runtime primitives smoke ok/,
187+
timeoutMs: 90_000,
188+
})
189+
console.log('smoke-binary: runtime primitives OK.')
190+
139191
const proc = spawn(binary, [], {
140192
stdio: ['ignore', 'pipe', 'pipe'],
141193
env: { ...process.env, NO_COLOR: '1', TERM: 'dumb' },

cli/src/index.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,22 @@ async function main(): Promise<void> {
322322
}
323323
}
324324

325+
// CI gate: `<binary> --smoke-runtime-primitives` exercises the highest-risk
326+
// compiled runtime integrations without entering the TUI: disk IO, vendored
327+
// native tools, subprocesses, and the real login-code HTTP endpoint.
328+
if (process.argv.includes('--smoke-runtime-primitives')) {
329+
try {
330+
const { runRuntimePrimitivesSmoke } = await import(
331+
'./smoke/runtime-primitives'
332+
)
333+
await runRuntimePrimitivesSmoke()
334+
process.exit(0)
335+
} catch (err) {
336+
console.error('runtime primitives smoke FAIL:', err)
337+
process.exit(1)
338+
}
339+
}
340+
325341
// Run OSC theme detection BEFORE anything else.
326342
// This MUST happen before OpenTUI starts because OSC responses come through stdin,
327343
// and OpenTUI also listens to stdin. Running detection here ensures stdin is clean.

0 commit comments

Comments
 (0)