import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, resolve } from "node:path"; import assert from "node:assert/strict"; import createLinuxCncInterpModule from "../../../build/wasm/core/linuxcnc_interp.js"; function allocCString(mod, value) { const bytes = mod.lengthBytesUTF8(value) + 1; const ptr = mod._malloc(bytes); mod.stringToUTF8(value, ptr, bytes); return ptr; } function runProgram(mod, programText) { const programPtr = allocCString(mod, programText); let resultPtr = 0; try { resultPtr = mod._lcinterp_run_program(programPtr); assert.notEqual(resultPtr, 0); return mod.UTF8ToString(resultPtr); } finally { if (resultPtr) { mod._lcinterp_free_string(resultPtr); } mod._free(programPtr); } } function runProgramWithIni(mod, programText, iniPath) { const programPtr = allocCString(mod, programText); const iniPathPtr = allocCString(mod, iniPath); let resultPtr = 0; try { resultPtr = mod._lcinterp_run_program_with_ini(programPtr, iniPathPtr); assert.notEqual(resultPtr, 0); return mod.UTF8ToString(resultPtr); } finally { if (resultPtr) { mod._lcinterp_free_string(resultPtr); } mod._free(programPtr); mod._free(iniPathPtr); } } function ensureDir(mod, path) { try { mod.FS.mkdir(path); } catch { // Directory already exists. } } function runFile(mod, path) { const pathPtr = allocCString(mod, path); let resultPtr = 0; try { resultPtr = mod._lcinterp_run_file(pathPtr); assert.notEqual(resultPtr, 0); return mod.UTF8ToString(resultPtr); } finally { if (resultPtr) { mod._lcinterp_free_string(resultPtr); } mod._free(pathPtr); } } function verifyExpectedOutput(fixtureName, output, expectedText) { const expectedLines = expectedText.split("\n").filter(Boolean); for (const expectedLine of expectedLines) { if (expectedLine.startsWith("absent=")) { const forbidden = expectedLine.slice("absent=".length); assert.equal( output.includes(forbidden), false, `${fixtureName}: unexpected ${forbidden}`, ); continue; } assert.equal( output.includes(expectedLine), true, `${fixtureName}: ${expectedLine}`, ); } } const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const rootDir = resolve(__dirname, "../../.."); const wasmPath = resolve(rootDir, "build/wasm/core/linuxcnc_interp.wasm"); const interp = await createLinuxCncInterpModule({ wasmBinary: readFileSync(wasmPath), print() {}, printErr(message) { console.error(message); }, }); const fixtureNames = [ "minimal_linear", "arc_semantics", "length_units", "modal_incremental", "plane_selection", "coordinate_offsets", "g53_machine_coordinates", "feed_control_modes", "position_params", "probe_semantics", "spindle_orient", "comment_logging", "numbered_params", "tool_semantics", "tool_table_setup", "tool_reload", "canned_cycles", "cutter_comp_motion", "threading_sync", "nurbs_g5_semantics", "nurbs_g6_semantics", "state_tag_motion", "canon_runtime_edges", "program_end_modal_reset", ]; for (const fixtureName of fixtureNames) { const programText = readFileSync( resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`), "utf8", ); const expectedEvents = readFileSync( resolve(rootDir, `tests/fixtures/canon/${fixtureName}.events`), "utf8", ).trimEnd(); verifyExpectedOutput(fixtureName, runProgram(interp, programText), expectedEvents); } ensureDir(interp, "/work"); const namedParamIniPath = "/work/namedparams.ini"; interp.FS.writeFile( namedParamIniPath, readFileSync(resolve(rootDir, "tests/fixtures/ini/namedparams.ini"), "utf8"), { encoding: "utf8" }, ); const namedParamProgramText = readFileSync( resolve(rootDir, "tests/fixtures/gcode/namedparam_semantics.ngc"), "utf8", ); const namedParamExpected = readFileSync( resolve(rootDir, "tests/fixtures/canon/namedparam_semantics.events"), "utf8", ).trimEnd(); verifyExpectedOutput( "namedparam_semantics", runProgramWithIni(interp, namedParamProgramText, namedParamIniPath), namedParamExpected, ); const errorFixtureNames = [ "g1_zero_feed", "arc_radius_mismatch", "arc_zero_radius", "cutter_comp_plane_change", "g53_incremental", "namedparam_readonly", "numbered_param_readonly", "tool_not_found", "tool_length_offset_not_found", ]; for (const fixtureName of errorFixtureNames) { const programText = readFileSync( resolve(rootDir, `tests/fixtures/gcode_errors/${fixtureName}.ngc`), "utf8", ); const expectedOutput = readFileSync( resolve(rootDir, `tests/fixtures/canon_errors/${fixtureName}.expected`), "utf8", ).trimEnd(); verifyExpectedOutput(fixtureName, runProgram(interp, programText), expectedOutput); } const fileFixtureNames = [ "minimal_linear", "arc_semantics", "length_units", "modal_incremental", "plane_selection", "coordinate_offsets", "g53_machine_coordinates", "feed_control_modes", "probe_semantics", "spindle_orient", "comment_logging", "numbered_params", "tool_semantics", "tool_table_setup", "tool_reload", "canned_cycles", "cutter_comp_motion", "threading_sync", "nurbs_g5_semantics", "nurbs_g6_semantics", "state_tag_motion", "canon_runtime_edges", "program_end_modal_reset", "file_open_reset", "percent_file_finish", "oword_subroutine", ]; for (const fixtureName of fileFixtureNames) { const programPath = `/work/${fixtureName}.ngc`; const programText = readFileSync( resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`), "utf8", ); const expectedEvents = readFileSync( resolve(rootDir, `tests/fixtures/canon/${fixtureName}.events`), "utf8", ).trimEnd(); interp.FS.writeFile(programPath, programText, { encoding: "utf8" }); verifyExpectedOutput(fixtureName, runFile(interp, programPath), expectedEvents); } console.log("interp_wasm_node_smoke=ok");