import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { WorkspaceRuntime } from "../workspace/runtime.mjs"; import { parseIni } from "../workspace/ini-parser.mjs"; import { ConfigurationStartupStateMachine, startConfiguration } from "../workspace/configuration.mjs"; import { SimSession, CONTEXT_ID } from "./sim-session.mjs"; const files = { "config/main.ini": "[HAL]\nHALFILE = machine.hal\n[RS274NGC]\nPROGRAM = main.ngc\n", "config/machine.hal": "setp sim.x 1\n", "config/main.ngc": "G0 X1\n", }; const relativeIniWorkspace = new WorkspaceRuntime({ files: { "config/nested/main.ini": "#INCLUDE ../shared.inc\n", "config/shared.inc": "[HAL]\nHALFILE = ../machine.hal\n", "machine.hal": "setp sim.x 1\n", } }); const relativeIni = parseIni({ workspace: relativeIniWorkspace, entrypoint: "config/nested/main.ini" }); assert.deepEqual(relativeIni.includes.map((item) => item.path), ["config/shared.inc"]); const manifest = [{ component: "sim" }]; const nativeCalls = []; let nativeSeq = 0; const nativeCore = { _sim_create() { nativeCalls.push("create"); return 0; }, _sim_load_workspace(path) { nativeCalls.push(`load:${path}`); return 0; }, }; const native = startConfiguration({ workspace: new WorkspaceRuntime({ files }), entrypoint: "config/main.ini", core: nativeCore, halManifest: manifest }); const nativeAgain = startConfiguration({ workspace: new WorkspaceRuntime({ files }), entrypoint: "config/main.ini", core: nativeCore, halManifest: manifest }); assert.equal(native.state, "TASK_READY"); assert.equal(native.status, "ready"); assert.equal(native.state_hash, nativeAgain.state_hash); assert.deepEqual(native.transitions.map((row) => row.to), ["NEW", "MOUNTED", "PARSED", "RESOLVED", "CORE_CREATED", "HAL_READY", "TASK_READY"]); assert.deepEqual(nativeCalls, ["create", "load:/workspace", "create", "load:/workspace"]); const wasm = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) }); const wasmReport = startConfiguration({ workspace: new WorkspaceRuntime({ files }), entrypoint: "config/main.ini", core: wasm, halManifest: manifest }); assert.equal(wasmReport.state, "TASK_READY"); assert.equal(wasmReport.state_hash, native.state_hash); const missing = startConfiguration({ workspace: new WorkspaceRuntime({ files: { "config/main.ini": "[HAL]\nHALFILE = missing.hal\n" } }), entrypoint: "config/main.ini", core: nativeCore }); assert.equal(missing.status, "blocked"); assert.equal(missing.state, "BLOCKED"); assert.equal(missing.diagnostics[0].source, "config/main.ini"); assert.equal(missing.diagnostics[0].line, 2); assert.equal(missing.resources.resources[0].code, "resource_missing"); const duplicate = startConfiguration({ workspace: new WorkspaceRuntime({ files: { ...files, "config/duplicate.ini": "[HAL]\nHALFILE = machine.hal\nHALFILE = machine.hal\n" } }), entrypoint: "config/duplicate.ini", core: nativeCore }); assert.equal(duplicate.status, "blocked"); assert.ok(duplicate.resources.resources.some((row) => row.code === "resource_duplicate")); const hostPath = startConfiguration({ workspace: new WorkspaceRuntime({ files: { "config/main.ini": "[HAL]\nHALFILE = /tmp/machine.hal\n" } }), entrypoint: "config/main.ini", core: nativeCore }); assert.equal(hostPath.status, "blocked"); assert.equal(hostPath.resources.resources[0].code, "resource_host_path"); const ambiguous = startConfiguration({ workspace: new WorkspaceRuntime({ files: { "config/main.ini": "[HAL]\nHALFILE = LIB:machine.hal\n", "linuxcnc/lib/machine.hal": "setp sim.x 1\n", "linuxcnc/lib/hallib/machine.hal": "setp sim.x 2\n", } }), entrypoint: "config/main.ini", core: nativeCore }); assert.equal(ambiguous.status, "ready"); assert.equal(ambiguous.resources.resources[0].path, "linuxcnc/lib/hallib/machine.hal"); const unsupportedHal = startConfiguration({ workspace: new WorkspaceRuntime({ files: { "config/main.ini": "[HAL]\nHALFILE = machine.hal\n", "config/machine.hal": "loadrt unknown names=u\n" } }), entrypoint: "config/main.ini", core: nativeCore, halManifest: [] }); assert.equal(unsupportedHal.status, "blocked"); assert.equal(unsupportedHal.diagnostics[0].source, "config/machine.hal"); assert.equal(unsupportedHal.diagnostics[0].line, 1); assert.equal(unsupportedHal.diagnostics[0].hal.records[0].code, "hal_component_undeclared"); let workerSeq = 0; const workerCore = { _sim_protocol_reset() {}, _sim_protocol_accept(...args) { if (args[1] !== workerSeq + 1) return -101; workerSeq = args[1]; return 0; }, _sim_create() { return 0; }, _sim_load_workspace() { return 0; }, }; const session = new SimSession(workerCore, { sessionId: "startup-session" }); const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: "startup-session" }; assert.equal(session.accept({ ...base, kind: "HELLO", seq: 1, payload: {} }).kind, "CAPABILITIES"); assert.equal(session.accept({ ...base, kind: "SESSION_ATTACH", seq: 2, operation_id: "attach", payload: {} }).kind, "SESSION_READY"); const workerResult = session.accept({ ...base, kind: "LOAD_WORKSPACE", seq: 3, operation_id: "startup", payload: { root_path: "/workspace", configuration: { entrypoint: "config/main.ini", files, halManifest: manifest }, } }); assert.equal(workerResult.kind, "COMMAND_RESULT"); assert.equal(workerResult.payload.configuration.state, "TASK_READY"); assert.equal(workerResult.payload.configuration.state_hash, native.state_hash); const workerBlocked = session.accept({ ...base, kind: "LOAD_WORKSPACE", seq: 4, operation_id: "blocked", payload: { root_path: "/workspace", configuration: { entrypoint: "config/main.ini", files: { "config/main.ini": "[HAL]\nHALFILE = missing.hal\n" } }, } }); assert.equal(workerBlocked.kind, "ERROR"); assert.equal(workerBlocked.payload.code, "configuration_startup"); assert.equal(workerBlocked.payload.startup.state, "BLOCKED"); console.log(`configuration_startup_runtime=passed state_hash=${native.state_hash} resource_hash=${native.resources.state_hash}`);