Files
orca/config/scripts/runtime-verification/desktop-transport/run-linux-cli-launch-contract-docker.mjs
T
Neil 6bb2e13c35 refactor(tooling): organize remaining scripts by domain owners (#38407)
* refactor(tooling): group remaining scripts by concrete domain owners

* fix(tooling): preserve relocated launcher resource roots
2026-11-05 00:29:16 -08:00

270 lines
8.8 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// Exercise packaged CLI paths under the hostile Linux conditions from #11609/#12530/#13719/#14229.
import { execFileSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
const commandArgs = process.argv.slice(2)
const appImageArg = valueAfter('--appimage')
const appImage = appImageArg ? resolve(appImageArg) : null
const platform = valueAfter('--platform')
const dockerPlatformArgs = platform ? ['--platform', platform] : []
const suffix = `${process.pid}-${Date.now()}`
const artifactVolume = `orca-cli-contract-artifact-${suffix}`
const tagArchitecture = platform?.split('/')[1] ?? process.arch
const tag = `orca-cli-launch-contract:ubuntu-24.04-${tagArchitecture}-${suffix}`
const containers = new Set()
let artifactVolumeCreated = false
const CASE_TIMEOUT_MS = 90_000
const BUILD_TIMEOUT_MS = 10 * 60_000
const STAGING_TIMEOUT_MS = 5 * 60_000
const DOCKER_TIMEOUT_MS = 2 * 60_000
const CLEANUP_TIMEOUT_MS = 30_000
// Exact statuses reject silent no-op launches as well as crashes.
const CASES = [
{
name: 'nofuse-userns-bundled-help',
expectStatus: 0,
expectOutput: 'Usage: orca <command>',
why: 'The bundled launcher must run with no FUSE, no display, and userns restricted (#11609, #12530).'
},
{
name: 'nofuse-userns-bundled-version',
expectStatus: 0,
expectOutput: /^\d+\.\d+\.\d+/m,
why: 'A deployment must be able to read the installed version without a display (#13719).'
},
{
name: 'nofuse-userns-bundled-status',
// No runtime is running; the CLI must report that itself.
expectStatus: 1,
expectOutput: 'appRunning',
why: 'A command that needs the runtime must report its absence, not abort.'
},
{
name: 'nofuse-userns-bundled-skills',
expectStatus: 0,
// Why: the rendered help header, not a bare 'skills' — the case name contains that word.
expectOutput: 'Usage: orca skills',
why: 'skills is a pure-text command that must never need Chromium (#14229).'
},
{
name: 'nofuse-userns-bundled-worktree',
expectStatus: 1,
expectOutput: "Orca is not running. Run 'orca open' first.",
why: 'A runtime-dependent command must report the missing runtime, not abort.'
},
{
name: 'nofuse-nosandbox-direct-binary-skills',
expectStatus: 0,
expectOutput: 'Usage: orca skills',
why: 'A direct binary launch that reaches JavaScript must run the command, not boot a GUI (#14229).'
},
{
name: 'nofuse-nosandbox-direct-binary-gui',
// A missing display is an expected diagnosis, not a crash.
expectStatus: 1,
expectOutput: 'needs a usable display server',
why: 'A desktop launch with no display must diagnose it instead of dying in uv_close (#13719).'
},
{
name: 'stale-display-nosandbox-direct-binary-gui',
expectStatus: 1,
expectOutput: 'needs a usable display server',
why: 'A stale DISPLAY value must diagnose the unreachable endpoint instead of dying in uv_close (#13719).'
}
]
try {
if (!appImage) {
fail(
'Usage: run-linux-cli-launch-contract-docker.mjs --appimage /path/to/orca-linux.AppImage [--platform linux/amd64|linux/arm64]'
)
}
if (commandArgs.includes('--platform') && !platform) {
fail('Missing value for --platform')
}
if (platform !== null && platform !== 'linux/amd64' && platform !== 'linux/arm64') {
fail(`Unsupported --platform: ${platform}`)
}
if (!existsSync(appImage)) {
fail(`AppImage not found: ${appImage}`)
}
docker(['volume', 'create', artifactVolume], { timeoutMs: DOCKER_TIMEOUT_MS })
artifactVolumeCreated = true
buildImage()
stageArtifacts()
runContract()
console.log('\nLinux CLI launch contract passed.')
} catch (error) {
console.error(error instanceof Error ? error.message : String(error))
process.exitCode = 1
} finally {
for (const container of containers) {
docker(['rm', '-f', container], { allowFailure: true, timeoutMs: CLEANUP_TIMEOUT_MS })
}
if (artifactVolumeCreated) {
docker(['volume', 'rm', artifactVolume], {
allowFailure: true,
timeoutMs: CLEANUP_TIMEOUT_MS
})
}
docker(['image', 'rm', tag], { allowFailure: true, timeoutMs: CLEANUP_TIMEOUT_MS })
}
function runContract() {
const failures = []
for (const testCase of CASES) {
const output = runCase(testCase.name)
const statusMatch = /^RESULT status=(\d+)/m.exec(output)
if (!statusMatch) {
failures.push(`${testCase.name}: ${firstLine(output)}\n ${testCase.why}`)
console.log(` FAIL ${testCase.name} — ${firstLine(output)}`)
continue
}
const status = Number(statusMatch[1])
// Avoid asserting harness’s RESULT case-name echo: expected substring must come from CLI output.
const commandOutput = output
.split('\n')
.filter((line) => !/^(?:RESULT|CRASHED|PRECONDITION_FAILED) /.test(line))
.join('\n')
const matchesOutput =
typeof testCase.expectOutput === 'string'
? commandOutput.includes(testCase.expectOutput)
: testCase.expectOutput.test(commandOutput)
if (status !== testCase.expectStatus || !matchesOutput) {
failures.push(
`${testCase.name}: expected status ${testCase.expectStatus} and ${testCase.expectOutput}, ` +
`got status ${status}\n ${testCase.why}`
)
console.log(` FAIL ${testCase.name} — status ${status}`)
continue
}
console.log(` ok ${testCase.name} (status ${status})`)
}
if (failures.length > 0) {
fail(`Linux CLI launch contract failed:\n - ${failures.join('\n - ')}`)
}
}
function runCase(caseName) {
const container = `orca-cli-contract-${caseName}-${suffix}`
containers.add(container)
// FUSE and extra capabilities would invalidate the test conditions.
return docker(
[
'run',
...dockerPlatformArgs,
'--name',
container,
'--rm',
'-v',
`${artifactVolume}:/artifacts`,
tag,
caseName
],
{ allowFailure: true, capture: true, timeoutMs: CASE_TIMEOUT_MS }
)
}
function buildImage() {
console.log(`Building ${tag}…`)
const buildArgs = [
'build',
...dockerPlatformArgs,
...(process.env.ORCA_CLI_FIXTURE_CACHE_IMAGE
? ['--cache-from', process.env.ORCA_CLI_FIXTURE_CACHE_IMAGE]
: []),
'-f',
'config/docker/cli-launch-contract/Dockerfile',
'-t',
tag,
'config/docker/cli-launch-contract'
]
// Why: apt fetches from archive.ubuntu.com stall or fail mid-sync; a second build usually lands on a healthy index.
try {
docker(buildArgs, { timeoutMs: BUILD_TIMEOUT_MS })
} catch (error) {
console.error(
`${error instanceof Error ? error.message : String(error)}\nRetrying docker build once…`
)
docker(buildArgs, { timeoutMs: BUILD_TIMEOUT_MS })
}
}
// Extract unprivileged so chrome-sandbox is not root-owned setuid.
function stageArtifacts() {
console.log('Staging the AppImage payload…')
const container = `orca-cli-contract-stage-${suffix}`
containers.add(container)
docker(
[
'run',
...dockerPlatformArgs,
'--name',
container,
'--rm',
'-v',
`${artifactVolume}:/artifacts`,
'-v',
`${appImage}:/input/orca-linux.AppImage:ro`,
'--entrypoint',
'bash',
tag,
'-lc',
[
'set -euo pipefail',
'cp /input/orca-linux.AppImage /artifacts/orca-linux.AppImage',
'chmod +x /artifacts/orca-linux.AppImage',
'chown -R orca:orca /artifacts',
// Use the AppImage runtime's no-FUSE extraction path.
'cd /artifacts && runuser --user orca -- ./orca-linux.AppImage --appimage-extract >/dev/null',
'test -x /artifacts/squashfs-root/resources/bin/orca-ide'
].join(' && ')
],
{ timeoutMs: STAGING_TIMEOUT_MS }
)
}
function docker(args, options = {}) {
try {
const output = execFileSync('docker', args, {
encoding: 'utf8',
stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
timeout: options.timeoutMs ?? DOCKER_TIMEOUT_MS,
killSignal: 'SIGTERM'
})
return output ?? ''
} catch (error) {
const timedOut = error instanceof Error && 'code' in error && error.code === 'ETIMEDOUT'
if (timedOut) {
const message = `docker ${args.join(' ')} timed out after ${options.timeoutMs ?? DOCKER_TIMEOUT_MS}ms`
if (!options.allowFailure) {
fail(message)
}
return message
}
if (!options.allowFailure) {
fail(
`docker ${args.join(' ')} failed: ${error instanceof Error ? error.message : String(error)}`
)
}
return `${error?.stdout ?? ''}${error?.stderr ?? ''}`
}
}
function firstLine(value) {
return (value ?? '').trim().split('\n')[0] || '(no output)'
}
function valueAfter(flag) {
const index = commandArgs.indexOf(flag)
return index === -1 ? null : (commandArgs[index + 1] ?? null)
}
function fail(message) {
throw new Error(message)
}