116 lines
6.9 KiB
TypeScript
116 lines
6.9 KiB
TypeScript
import { strict as assert } from 'node:assert'
|
|
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { spawnSync } from 'node:child_process'
|
|
import { test } from 'node:test'
|
|
import { createRequire } from 'node:module'
|
|
import ts from 'typescript'
|
|
const { yahboomDevicePreflight, yahboomModuleCases, yahboomRuntimeImage, yahboomRuntimeArtifacts, yahboomSimulation } = createRequire(import.meta.url)('./yahboomRuntime.ts') as typeof import('./yahboomRuntime')
|
|
|
|
test('simulation is an explicit opt-in and excludes physical device modules', () => {
|
|
for (const role of ['vehicle', 'slam', 'map', 'localization', 'planning', 'control']) {
|
|
const folder = `[lms-nav2-yahboom-${role}]yahboom_${role}_v1.0.0_ws`
|
|
assert.equal(yahboomSimulation(folder, ''), false)
|
|
assert.equal(yahboomSimulation(folder, 'sim_mode:=false'), false)
|
|
assert.equal(yahboomSimulation(folder, 'foo:=bar sim_mode:=true'), true)
|
|
}
|
|
for (const role of ['agent', 'camera']) {
|
|
assert.equal(yahboomSimulation(`[lms-nav2-yahboom-${role}]yahboom_${role}_v1.0.0_ws`, 'sim_mode:=true'), false)
|
|
}
|
|
assert.equal(yahboomSimulation('[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws', 'sim_mode:=true'), false)
|
|
})
|
|
|
|
test('local simulation overrides ARM metadata and downloads only the PC image', async () => {
|
|
const source = ts.createSourceFile('index.ts', readFileSync(new URL('./index.ts', import.meta.url), 'utf8'), ts.ScriptTarget.Latest, true)
|
|
const declaration = source.statements.find((item): item is ts.FunctionDeclaration => ts.isFunctionDeclaration(item) && item.name?.text === 'resolveLocalModuleRuntime')
|
|
assert.ok(declaration)
|
|
const artifact = { image: 'future-nav2-gazebo:humble', url: 'https://example.com/gazebo.tar.zst' }
|
|
const resolver = new Function('yahboomSimulation', 'defaultNav2GazeboRuntimeImage', 'resolveCustomModuleRuntimeImage',
|
|
ts.transpile(declaration.getText(source), { target: ts.ScriptTarget.ES2022 }) + '; return resolveLocalModuleRuntime;'
|
|
)(yahboomSimulation, artifact, () => { throw new Error('ARM metadata must not override simulation') })
|
|
const result = await resolver('/module', '[lms-nav2-yahboom-vehicle]yahboom_vehicle_v1.0.0_ws', 'sim_mode:=true',
|
|
[{ image: 'future-nav2:humble-jetson_orin', url: 'https://example.com/arm.tar.zst' }])
|
|
assert.deepEqual(result, { image: artifact.image, artifacts: [artifact] })
|
|
})
|
|
|
|
test('remote Yahboom downloads only its own image and preserves explicit overrides', () => {
|
|
const configured = [{ image: 'future-nav2:humble-jetson_orin', url: 'https://example.com/jetson.tar.zst' }]
|
|
assert.equal(yahboomRuntimeArtifacts('[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws', configured[0].image, configured), configured)
|
|
const folder = '[lms-nav2-yahboom-agent]yahboom_agent_v1.0.0_ws'
|
|
const image = 'future-nav2:humble-yahboom-agent'
|
|
const selected = yahboomRuntimeArtifacts(folder, image, configured)
|
|
assert.equal(selected?.length, 1)
|
|
assert.equal(selected?.[0].image, image)
|
|
assert.match(selected?.[0].checksumSha256 ?? '', /^[a-f0-9]{64}$/)
|
|
const override = { image, url: 'https://example.com/override.tar.zst' }
|
|
assert.deepEqual(yahboomRuntimeArtifacts(folder, image, [...configured, override]), [override])
|
|
assert.deepEqual(yahboomRuntimeArtifacts(folder, 'custom:tag', configured), [])
|
|
})
|
|
|
|
function runtimeScript(name: string): string {
|
|
const source = ts.createSourceFile('index.ts', readFileSync(new URL('./index.ts', import.meta.url), 'utf8'), ts.ScriptTarget.Latest, true)
|
|
const declaration = source.statements.find((item): item is ts.FunctionDeclaration => ts.isFunctionDeclaration(item) && item.name?.text === name)
|
|
assert.ok(declaration)
|
|
const code = ts.transpile(declaration.getText(source), { target: ts.ScriptTarget.ES2022 })
|
|
return new Function('yahboomModuleCases', 'yahboomDevicePreflight', `${code}; return ${name}();`)(yahboomModuleCases, yahboomDevicePreflight)
|
|
}
|
|
|
|
test('Yahboom image routing does not change MKMini, Gazebo or custom modules', () => {
|
|
assert.equal(yahboomRuntimeImage('[lms-nav2-yahboom-agent]yahboom_agent_v1.0.0_ws'), 'future-nav2:humble-yahboom-agent')
|
|
assert.equal(yahboomRuntimeImage('[lms-nav2-yahboom-slam]yahboom_slam_v1.0.0_ws'), 'future-nav2:humble-yahboom')
|
|
for (const name of ['[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws', '[lms-nav2-gazebo-map]nav2-map_v1.0.0_ws', '[lms-nav2-custom]yahboom_agent_v1.0.0_ws', '[lms-nav2-yahboom-agent]other_v1.0.0_ws']) {
|
|
assert.equal(yahboomRuntimeImage(name), null)
|
|
}
|
|
})
|
|
|
|
test('all eight templates map to their own package in both runtime scripts', () => {
|
|
for (const build of [false, true]) {
|
|
for (const role of ['agent', 'vehicle', 'slam', 'map', 'localization', 'planning', 'control', 'camera']) {
|
|
const script = ['case "$1" in', ...yahboomModuleCases(build), 'esac', build ? 'printf "%s" "$TARGET"' : 'printf "%s %s" "$PACKAGE" "$LAUNCH"'].join('\n')
|
|
const result = spawnSync('bash', ['-c', script, '_', `[lms-nav2-yahboom-${role}]yahboom_${role}_v1.0.0_ws`], { encoding: 'utf8' })
|
|
assert.equal(result.status, 0, result.stderr)
|
|
assert.equal(result.stdout, build ? `yahboom_${role}` : `yahboom_${role} ${role}.launch.py`)
|
|
}
|
|
}
|
|
})
|
|
|
|
test('generated build and run shell scripts are syntactically valid', () => {
|
|
for (const name of ['buildNav2DockerRunScript', 'buildNav2DockerBuildScript']) {
|
|
const result = spawnSync('bash', ['-n'], { input: runtimeScript(name), encoding: 'utf8' })
|
|
assert.equal(result.status, 0, result.stderr)
|
|
}
|
|
})
|
|
|
|
test('invalid hardware path fails before Docker is called; MKMini still launches without extra device', () => {
|
|
const root = mkdtempSync(join(tmpdir(), 'yahboom-runtime-'))
|
|
try {
|
|
const bin = join(root, 'bin')
|
|
mkdirSync(bin)
|
|
writeFileSync(join(bin, 'docker'), '#!/bin/bash\nprintf "%s\\n" "$@"\n', { mode: 0o755 })
|
|
const script = join(root, 'docker_run_nav2.sh')
|
|
writeFileSync(script, runtimeScript('buildNav2DockerRunScript'))
|
|
for (const [folder, parameter] of [
|
|
['[lms-nav2-yahboom-agent]yahboom_agent_v1.0.0_ws', 'serial_device:=/etc/passwd'],
|
|
['[lms-nav2-yahboom-camera]yahboom_camera_v1.0.0_ws', 'video_device:=/dev/../etc/passwd'],
|
|
['[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws', ''],
|
|
]) {
|
|
mkdirSync(join(root, folder, 'install'), { recursive: true })
|
|
writeFileSync(join(root, folder, 'install/setup.bash'), '')
|
|
const result = spawnSync('bash', [script, 'test-container', root, folder, 'test-image', '77', 'false', parameter], {
|
|
encoding: 'utf8', env: { ...process.env, PATH: `${bin}:${process.env.PATH}`, DISPLAY: '', FASTDDS_CONFIG_PATH: '' },
|
|
})
|
|
if (parameter) {
|
|
assert.equal(result.status, 64)
|
|
assert.equal(result.stdout, '')
|
|
} else {
|
|
assert.equal(result.status, 0, result.stderr)
|
|
assert.match(result.stdout, /vehicle_pkg\nvehicle.launch.py/)
|
|
assert.doesNotMatch(result.stdout, /ttyUSB|video0|privileged/)
|
|
}
|
|
}
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true })
|
|
}
|
|
})
|