browser-harness sends each CLI event from a detached python child; in the smoke it outlived the check and held the relocated payload open on Windows, so the restore rename failed with EPERM. Hermes's own harness env sets ANONYMIZED_TELEMETRY=false; the smoke now does the same.
66 lines
3.5 KiB
Bash
66 lines
3.5 KiB
Bash
#!/usr/bin/env bash
|
|
# Exercise the published launch contract after relocation. Network isolation is
|
|
# the runner's responsibility (Linux CI uses unshare --net), not a lazy-deps flag.
|
|
set -euo pipefail
|
|
PAYLOAD="${1:?usage: smoke-payload.sh <payload-dir>}"
|
|
command -v cygpath >/dev/null 2>&1 && PAYLOAD="$(cygpath -w "$PAYLOAD")"
|
|
node --input-type=module - "$PAYLOAD" <<'JS'
|
|
import { mkdtempSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync } from 'node:fs'
|
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
|
|
import { spawnSync } from 'node:child_process'
|
|
|
|
const original = resolve(process.argv[2])
|
|
// Same filesystem: moving a multi-GB payload should not copy it.
|
|
const scratch = mkdtempSync(join(dirname(original), '.payload-smoke-'))
|
|
const moved = join(scratch, 'relocated payload')
|
|
let relocated = false
|
|
try {
|
|
renameSync(original, moved)
|
|
relocated = true
|
|
const manifest = JSON.parse(readFileSync(join(moved, 'manifest.json'), 'utf8'))
|
|
const command = manifest.runtime.commands.hermes
|
|
if (!command) throw new Error('manifest has no hermes command')
|
|
const executable = realpathSync(resolve(moved, command))
|
|
const inside = relative(realpathSync(moved), executable)
|
|
if (isAbsolute(command) || isAbsolute(inside) || inside === '..' || inside.startsWith(`..${sep}`)) {
|
|
throw new Error('manifest command escapes the relocated payload')
|
|
}
|
|
const home = join(scratch, 'home')
|
|
mkdirSync(home)
|
|
const env = { HOME: home, USERPROFILE: home, HERMES_HOME: join(home, '.hermes'),
|
|
PYTHONUTF8: '1', PYTHONDONTWRITEBYTECODE: '1', HERMES_DISABLE_LAZY_INSTALLS: '1',
|
|
UV_OFFLINE: '1', npm_config_offline: 'true' }
|
|
// Keep OS process necessities only; no checkout, Python, PM, or Node overrides.
|
|
for (const [key, value] of Object.entries(process.env)) {
|
|
if (/^(path|systemroot|windir|comspec|pathext|temp|tmp)$/i.test(key)) env[key] = value
|
|
}
|
|
// tools list crosses the real application/config/registry imports, unlike
|
|
// version/help and PM's stdlib bootstrap fast paths.
|
|
for (const args of [['--version'], ['--help'], ['tools', 'list'], ['pm', 'doctor']]) {
|
|
console.log(`— published hermes ${args.join(' ')} (relocated) —`)
|
|
const child = spawnSync(executable, args, {
|
|
cwd: home, env, stdio: 'inherit', timeout: 120000,
|
|
})
|
|
if (child.error) throw child.error
|
|
if (child.status !== 0) throw new Error(`hermes ${args.join(' ')} failed: ${child.status ?? child.signal}`)
|
|
}
|
|
// browser_exec's engine (browser-harness) runs on the store interpreter with only the
|
|
// payload's site dir on PYTHONPATH (tools/browser_use_cli.py); -S drops any site of its own.
|
|
// Telemetry off as at runtime: its detached sender would outlive the check and pin the
|
|
// payload dir on Windows (EPERM on the restore rename).
|
|
console.log('— browser-harness on the store interpreter (relocated) —')
|
|
const harness = spawnSync(resolve(moved, manifest.runtime.storePython),
|
|
['-S', '-m', 'browser_harness.run', '--version'], {
|
|
cwd: home,
|
|
env: { ...env, ANONYMIZED_TELEMETRY: 'false', PYTHONPATH: resolve(moved, manifest.runtime.sitePackages) },
|
|
stdio: 'inherit', timeout: 120000,
|
|
})
|
|
if (harness.error) throw harness.error
|
|
if (harness.status !== 0) throw new Error(`browser-harness failed: ${harness.status ?? harness.signal}`)
|
|
console.log('SMOKE OK')
|
|
} finally {
|
|
// Restore even after a command fails, so its artifact remains inspectable.
|
|
if (relocated) renameSync(moved, original)
|
|
rmSync(scratch, { recursive: true, force: true })
|
|
}
|
|
JS |