Files
cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/tests/node/verify_native_task_hal_audit.mjs
2026-07-02 08:01:34 -04:00

212 lines
7.9 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { spawnSync } from "node:child_process";
import { createFullLinuxCncExecutionBoundary } from "../../app/src/runtime/full-execution-boundary.js";
import { createNativeTaskHalReadinessAudit } from "../../app/src/runtime/native-task-hal-audit.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectDir = resolve(__dirname, "../..");
const rootDir = resolve(projectDir, "..");
const phase0Script = resolve(rootDir, "wasm-port/tests/native/verify_task_hal_phase0.sh");
const manifestLog = resolve(rootDir, "wasm-port/build/task-hal/verify_task_hal_source_manifest.stdout.log");
const probeLog = resolve(rootDir, "wasm-port/build/task-hal/probe_trt_task_hal_runtime.stdout.log");
const manifestReport = resolve(rootDir, "wasm-port/build/task-hal/task-hal-source-manifest.tsv");
const artifactDir = resolve(projectDir, "build/readiness");
const artifactPath = resolve(artifactDir, "native-task-hal-readiness.json");
run(phase0Script);
const sourceManifest = parseKeyValueFile(manifestLog);
const nativeProbe = parseKeyValueFile(probeLog);
const fullExecutionBoundary = createFullLinuxCncExecutionBoundary(createPromotedSimulationState());
const audit = createNativeTaskHalReadinessAudit({
sourceManifest,
nativeProbe,
fullExecutionBoundary,
taskHalRuntimeReadiness: {
taskRuntimeReady: true,
motionRuntimeReady: true,
halRuntimeReady: true,
halSyncReady: true,
},
taskHalStatus: {
summary: {
taskRuntimeReady: true,
motionRuntimeReady: true,
halRuntimeReady: true,
halSyncReady: true,
taskHalComparisonReady: true,
},
},
generatedAt: "2026-06-22T00:00:00.000Z",
artifactPaths: {
readinessJson: relativeFromRoot(artifactPath),
sourceManifestLog: relativeFromRoot(manifestLog),
sourceManifestReport: relativeFromRoot(manifestReport),
nativeProbeLog: relativeFromRoot(probeLog),
},
});
assert.equal(audit.status, "ok");
assert.equal(audit.taskHalWebSimulationBoundaryConsistent, true);
assert.equal(audit.webSimulation.promoted, true);
assert.equal(audit.webSimulation.nativeTaskReady, true);
assert.equal(audit.webSimulation.nativeHalSyncReady, true);
assert.equal(audit.webSimulation.fullLinuxCncProgramExecutionReady, true);
assert.equal(audit.nativeHostAndHardware.nativeProbe, "ok");
assert.match(audit.nativeHostAndHardware.nativeProbeStatus, /^(passed|ready_disabled_by_default|skipped_missing_host_runtime)$/);
assert.equal(audit.nativeHostAndHardware.nativePromotionAllowed, false);
assert.equal(audit.nativeHostAndHardware.hardwareDrive, false);
assert.equal(audit.nativeHostAndHardware.hostRealtimeKernel, false);
assert.equal(audit.nativeHostAndHardware.externalUserMProcessReady, false);
assert.equal(audit.nativeHostAndHardware.toolDbProcessReady, false);
assert.equal(audit.nativeHostAndHardware.hostExternalUserMProcessReady, false);
assert.equal(audit.nativeHostAndHardware.hostToolDbProcessReady, false);
assert.equal(audit.nativeHostAndHardware.arbitraryUserMExecution, false);
assert.equal(audit.toolUserWebSimulation.externalUserMProcessReady, true);
assert.equal(audit.toolUserWebSimulation.externalUserMProcessScope, "web_simulation_only");
assert.equal(audit.toolUserWebSimulation.toolDbProcessReady, true);
assert.equal(audit.toolUserWebSimulation.toolDbProcessScope, "web_simulation_only");
assert.equal(audit.toolUserWebSimulation.ready, true);
assert.equal(audit.sourceManifest.ready, true);
assert.equal(audit.sourceManifest.taskSourceCount > 0, true);
assert.equal(audit.sourceManifest.halSourceCount > 0, true);
assert.equal(audit.sourceManifest.motionSourceCount > 0, true);
assert.equal(audit.gates.task_hal_web_simulation_boundary_consistent, 1);
assert.equal(audit.gates.hardware_drive, 0);
assert.equal(audit.gates.host_realtime_kernel, 0);
assert.equal(audit.gates.external_user_m_process_ready, 1);
assert.equal(audit.gates.tool_db_process_ready, 1);
assert.equal(audit.gates.host_external_user_m_process_ready, 0);
assert.equal(audit.gates.host_tool_db_process_ready, 0);
assert.equal(audit.gates.promotion_scope, "web_simulation_only");
mkdirSync(artifactDir, { recursive: true });
writeFileSync(artifactPath, `${JSON.stringify(audit, null, 2)}\n`);
const saved = JSON.parse(readFileSync(artifactPath, "utf8"));
assert.equal(saved.apiName, "web-rtcp-5axis-native-task-hal-readiness-audit");
assert.equal(saved.status, "ok");
console.log("native_task_hal_source_artifact_audit=ok");
console.log(`native_task_hal_readiness_artifact=${relativeFromRoot(artifactPath)}`);
console.log("task_hal_web_simulation_boundary_consistent=1");
console.log(`native_task_hal_host_probe_status=${audit.nativeHostAndHardware.nativeProbeStatus}`);
console.log("hardware_drive=0");
console.log("host_realtime_kernel=0");
console.log("external_user_m_process_ready=1");
console.log("tool_db_process_ready=1");
console.log("host_external_user_m_process_ready=0");
console.log("host_tool_db_process_ready=0");
console.log("promotion_scope=web_simulation_only");
function createPromotedSimulationState() {
const programExecution = {
sourceMode: "linuxcnc-interpreter-wasm",
plannerTiming: {
plannerRuntimeReady: true,
semanticBoundary: "linuxcnc_tp_queue_runtime_timing_from_canonical_motion",
},
summary: {
motionEventCount: 3,
canonicalEventCount: 8,
plannerRuntimeReady: true,
},
};
return {
machineProfile: "xyzac-trt",
linuxCncBoundaryAdapter: {
linuxCncKinematicsReady: true,
linuxCncInterpreterReady: true,
},
rtcpFrame: {
semanticBoundary: "linuxcnc_kinematics_wasm_c_abi",
readiness: { linuxCncKinematicsReady: true },
},
interpreterRuntimeReadiness: {
loaded: true,
semanticBoundary: "linuxcnc_interpreter_wasm_canonical_events",
},
programExecution,
machineFileStaging: {
status: "staged",
fileCount: 12,
},
machineFileExecution: {
sourceMode: "linuxcnc-machine-file-remap-wasm",
summary: { machineFileExecutionReady: true },
resultText: [
"fiveaxis_ini_open=1",
"fiveaxis_remaps_ready=1",
"fiveaxis_file_reached_exit=1",
"fiveaxis_hal_switchkins: rc=0 found=1 value=1",
].join("\n"),
},
taskHalRuntimeReadiness: {
taskRuntimeReady: true,
motionRuntimeReady: true,
halRuntimeReady: true,
halSyncReady: true,
},
taskHalStatus: {
summary: {
taskRuntimeReady: true,
motionRuntimeReady: true,
halRuntimeReady: true,
halSyncReady: true,
taskHalComparisonReady: true,
},
ui: {
taskCycle: 2,
servoCycle: 20,
motionQueueDepth: 0,
halChangedPinCount: 4,
},
},
toolDbReadiness: {
toolDbProcessReady: true,
toolDbProcessScope: "web_simulation_only",
hostToolDbProcessReady: false,
toolCount: 10,
},
controlledUserMReadiness: {
externalUserMProcessReady: true,
externalUserMProcessScope: "web_simulation_only",
hostExternalUserMProcessReady: false,
arbitraryUserMExecution: false,
},
};
}
function run(scriptPath) {
const result = spawnSync("bash", [scriptPath], {
cwd: rootDir,
encoding: "utf8",
});
if (result.status !== 0) {
process.stdout.write(result.stdout || "");
process.stderr.write(result.stderr || "");
throw new Error(`${scriptPath} failed with status ${result.status}`);
}
}
function parseKeyValueFile(path) {
const fields = {};
for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
if (!line || line.startsWith("#")) continue;
const equals = line.indexOf("=");
if (equals <= 0) continue;
fields[line.slice(0, equals)] = line.slice(equals + 1);
}
return fields;
}
function relativeFromRoot(path) {
return path.startsWith(`${rootDir}/`) ? path.slice(rootDir.length + 1) : path;
}