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 }) } })