Files
cnc_wams/wasm-port/tests/wasm/node/verify_interp_wasm.mjs
wangdequan 657eecfcc3 按规划继续工作
结论:新增 namedparam_ini_semantics 夹具,将本地命名参数和 _ini[...] 查询纳入 upstream rs274 -i 基线,并接入 Node WASM 与浏览器 INI-aware 解释器 smoke;HAL 查询继续保留在 standalone adapter 边界。
2026-06-08 12:18:02 +08:00

469 lines
14 KiB
JavaScript

import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import { createLinuxCncInterpSdk } from "../../../runtime/sdk/src/index.js";
import {
INTERP_BASELINE_INI_FIXTURE,
INTERP_ERROR_FIXTURES,
INTERP_FILE_FIXTURES,
INTERP_INI_FIXTURE,
INTERP_MDI_FIXTURES,
INTERP_POSITION_PARAMS_FILE_FIXTURE,
} from "../../fixtures/interp-fixture-matrix.mjs";
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}`,
);
}
}
function verifyExpectedFileError(fixtureName, output, expectedText) {
assert.equal(
output.includes("file_open=0"),
true,
`${fixtureName}: file open`,
);
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;
}
if (expectedLine.startsWith("error_text=")) {
const message = expectedLine.slice("error_text=".length);
assert.equal(
output.includes(`file_error_text=${message}`),
true,
`${fixtureName}: ${message}`,
);
continue;
}
if (expectedLine.startsWith("canon_event=")) {
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 createLinuxCncInterpSdk({
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
console.error(message);
},
});
const namedParamIniPath = "/work/namedparams.ini";
interp.writeTextFile(
namedParamIniPath,
readFileSync(resolve(rootDir, "tests/fixtures/ini/namedparams.ini"), "utf8"),
);
verifyExpectedOutput(
"probe_init_and_synch",
interp.probeInitAndSynch(),
[
"init=0",
"setup.length_units=2",
"setup.origin_index=1",
"setup.distance_mode=0",
"setup.feed_mode=0",
"setup.motion_mode=800",
"canon_event=INIT_CANON",
"canon_event=USE_LENGTH_UNITS units=2",
"canon_event=SET_G5X_OFFSET index=1",
"canon_event=SET_G92_OFFSET x=0 y=0 z=0",
"canon_event=SET_XY_ROTATION rotation=0",
"canon_event=SET_FEED_REFERENCE reference=2",
"inch_init=0",
"inch_setup.length_units=1",
"inch_external_length_units=0.0393701",
"inch_canon_event=USE_LENGTH_UNITS units=1",
"synch=0",
"synch.current_pocket=2",
"synch.selected_pocket=2",
"synch.tool_0=2",
"synch.tool_2=2",
].join("\n"),
);
verifyExpectedOutput(
"probe_indexer",
interp.probeIndexer(),
[
"execute=0",
"post_a=90",
"canon_event=SET_MOTION_CONTROL_MODE mode=2 tolerance=0",
"canon_event=UNLOCK_ROTARY line=1 joint=0",
"canon_event=STRAIGHT_TRAVERSE line=1 x=0 y=0 z=0 a=90 b=0 c=0 u=0 v=0 w=0",
"canon_event=LOCK_ROTARY line=1 joint=0",
"canon_event=SET_MOTION_CONTROL_MODE mode=1 tolerance=0",
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=0",
"canon_event=UPDATE_TAG line=1",
].join("\n"),
);
verifyExpectedOutput(
"probe_named_parameters",
interp.probeNamedParameters(namedParamIniPath),
[
"init_named_parameters=0",
"global_named_count=57",
"_metric_machine: rc=0 found=1 value=1",
"_motion_mode: rc=0 found=1 value=10",
"_metric: rc=0 found=1 value=1",
"_feed: rc=0 found=1 value=123.45",
"_rpm: rc=0 found=1 value=678.9",
"_x: rc=0 found=1 value=1.25",
"_current_tool: rc=0 found=1 value=12",
"_ini[traj]max_linear_velocity: rc=0 found=1 value=35",
"_hal[standalone.pin-bit]: rc=0 found=1 value=1",
"_hal[standalone.signal-float]: rc=0 found=1 value=98.25",
"_hal[standalone.param-s32]: rc=0 found=1 value=-17",
"_hal[standalone.pin-u32]: rc=0 found=1 value=1.23457e+08",
"_hal[standalone.signal-s64]: rc=0 found=1 value=-9e+09",
"_hal[standalone.param-u64]: rc=0 found=1 value=9e+09",
"_hal[standalone.disconnected-float]: rc=0 found=1 value=12.5",
"_hal[standalone.missing]: rc=0 found=0 value=0",
].join("\n"),
);
for (const fixtureName of INTERP_MDI_FIXTURES) {
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, interp.runProgram(programText), expectedEvents);
}
const namedParamProgramText = readFileSync(
resolve(rootDir, `tests/fixtures/gcode/${INTERP_INI_FIXTURE}.ngc`),
"utf8",
);
const namedParamExpected = readFileSync(
resolve(rootDir, `tests/fixtures/canon/${INTERP_INI_FIXTURE}.events`),
"utf8",
).trimEnd();
verifyExpectedOutput(
INTERP_INI_FIXTURE,
interp.runProgramWithIni(namedParamProgramText, namedParamIniPath),
namedParamExpected,
);
const baselineIniProgramText = readFileSync(
resolve(rootDir, `tests/fixtures/gcode/${INTERP_BASELINE_INI_FIXTURE}.ngc`),
"utf8",
);
const baselineIniExpected = readFileSync(
resolve(rootDir, `tests/fixtures/canon/${INTERP_BASELINE_INI_FIXTURE}.events`),
"utf8",
).trimEnd();
verifyExpectedOutput(
INTERP_BASELINE_INI_FIXTURE,
interp.runProgramWithIni(baselineIniProgramText, namedParamIniPath),
baselineIniExpected,
);
for (const fixtureName of INTERP_ERROR_FIXTURES) {
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, interp.runProgram(programText), expectedOutput);
}
for (const fixtureName of INTERP_ERROR_FIXTURES) {
const programPath = `/work/${fixtureName}-error.ngc`;
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();
interp.writeTextFile(programPath, programText);
verifyExpectedFileError(fixtureName, interp.runFile(programPath), expectedOutput);
}
for (const fixtureName of INTERP_FILE_FIXTURES) {
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.writeTextFile(programPath, programText);
verifyExpectedOutput(fixtureName, interp.runFile(programPath), expectedEvents);
}
const namedParamFilePath = `/work/${INTERP_INI_FIXTURE}.ngc`;
interp.writeTextFile(namedParamFilePath, namedParamProgramText);
verifyExpectedOutput(
"namedparam_semantics_file",
interp.runFileWithIni(namedParamFilePath, namedParamIniPath),
namedParamExpected,
);
const baselineIniFilePath = `/work/${INTERP_BASELINE_INI_FIXTURE}.ngc`;
interp.writeTextFile(baselineIniFilePath, baselineIniProgramText);
verifyExpectedOutput(
`${INTERP_BASELINE_INI_FIXTURE}_file`,
interp.runFileWithIni(baselineIniFilePath, namedParamIniPath),
baselineIniExpected,
);
const positionParamsFilePath = "/work/position_params.ngc";
interp.writeTextFile(
positionParamsFilePath,
readFileSync(
resolve(rootDir, `tests/fixtures/gcode/${INTERP_POSITION_PARAMS_FILE_FIXTURE}.ngc`),
"utf8",
),
);
verifyExpectedOutput(
`${INTERP_POSITION_PARAMS_FILE_FIXTURE}_file`,
interp.runFile(positionParamsFilePath),
readFileSync(
resolve(
rootDir,
`tests/fixtures/canon_file/${INTERP_POSITION_PARAMS_FILE_FIXTURE}.events`,
),
"utf8",
).trimEnd(),
);
const parameterFilePath = "/work/rs274ngc.var";
interp.writeTextFile(
parameterFilePath,
[
"5161 10.5",
"5162 20.25",
"5220 1",
"5221 2.25",
"5399 44",
"<_named_param> 123",
"",
].join("\n"),
);
verifyExpectedOutput(
"restore_parameters",
interp.restoreParameters(parameterFilePath),
[
"restore_parameters=0",
"parameter_5161=10.5",
"parameter_5162=20.25",
"parameter_5220=1",
"parameter_5221=2.25",
"parameter_5399=44",
].join("\n"),
);
verifyExpectedOutput(
"restore_parameters_missing_file",
interp.restoreParameters("/work/missing.var"),
"restore_parameters=0",
);
const outOfOrderParameterFilePath = "/work/out-of-order.var";
interp.writeTextFile(outOfOrderParameterFilePath, "5220 1\n5161 2\n");
verifyExpectedOutput(
"restore_parameters_out_of_order",
interp.restoreParameters(outOfOrderParameterFilePath),
[
"restore_parameters=5",
"restore_error_text=Parameter file out of order",
].join("\n"),
);
const missingRequiredParameterFilePath = "/work/missing-required.var";
interp.writeTextFile(missingRequiredParameterFilePath, "5161 3.5\n5220 1\n");
verifyExpectedOutput(
"restore_parameters_missing_required",
interp.restoreParameters(missingRequiredParameterFilePath),
[
"restore_parameters=0",
"parameter_5161=3.5",
"parameter_5162=0",
].join("\n"),
);
verifyExpectedOutput(
"save_parameters",
interp.saveParameters(parameterFilePath, {
5161: 12.34,
5162: 56.78,
5220: 1.0,
5221: 9.87,
5399: 66.6,
}),
[
"save_parameters=0",
"parameter_5161=12.34",
"parameter_5162=56.78",
"parameter_5220=1",
"parameter_5221=9.87",
"parameter_5399=66.6",
].join("\n"),
);
const savedParameterFile = interp.readTextFile(parameterFilePath);
const backupParameterFile = interp.readTextFile(`${parameterFilePath}.bak`);
assert.equal(savedParameterFile.includes("5161\t12.340000"), true);
assert.equal(savedParameterFile.includes("5162\t56.780000"), true);
assert.equal(savedParameterFile.includes("5221\t9.870000"), true);
assert.equal(savedParameterFile.includes("5399\t66.600000"), true);
assert.equal(savedParameterFile.includes("_named_param"), false);
assert.equal(backupParameterFile.includes("5161 10.5"), true);
const toolTablePath = "/work/tool.tbl";
interp.writeTextFile(
toolTablePath,
[
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;finish tool",
"T5 P9 X1 Y2 Z3 ;rough tool",
"",
].join("\n"),
);
verifyExpectedOutput(
"load_tool_table",
interp.loadToolTable(toolTablePath),
[
"tooldata_load=0",
"tooldata_last_index=2",
"tool_0.toolno=-1",
"tool_1.toolno=2",
"tool_1.pocketno=7",
"tool_1.z=3.125",
"tool_1.diameter=1.5",
"tool_1.frontangle=12",
"tool_1.backangle=34",
"tool_1.orientation=4",
"tool_1.comment=finish tool",
"tool_2.toolno=5",
"tool_2.pocketno=9",
"tool_2.z=3",
"tool_2.comment=rough tool",
"tool_index_for_tool_2=1",
].join("\n"),
);
verifyExpectedOutput(
"save_tool_table",
interp.saveToolTable(toolTablePath),
[
"tooldata_save=0",
"tool_1.toolno=2",
"tool_2.toolno=5",
].join("\n"),
);
const savedToolTable = interp.readTextFile(toolTablePath);
assert.equal(savedToolTable.includes("T2"), true);
assert.equal(savedToolTable.includes("P7"), true);
assert.equal(savedToolTable.includes("D+1.500000"), true);
assert.equal(savedToolTable.includes("Z+3.125000"), true);
assert.equal(savedToolTable.includes("I+12.000000"), true);
assert.equal(savedToolTable.includes("J+34.000000"), true);
assert.equal(savedToolTable.includes("Q4"), true);
assert.equal(savedToolTable.includes(";finish tool"), true);
assert.equal(savedToolTable.includes("T5"), true);
assert.equal(savedToolTable.includes("P9"), true);
assert.equal(savedToolTable.includes(";rough tool"), true);
const randomToolTablePath = "/work/random-tool.tbl";
interp.writeTextFile(
randomToolTablePath,
[
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;random finish tool",
"T5 P9 X1 Y2 Z3 ;random rough tool",
"",
].join("\n"),
);
verifyExpectedOutput(
"load_tool_table_random",
interp.loadToolTable(randomToolTablePath, { randomToolChanger: true }),
[
"tooldata_random_toolchanger=1",
"tooldata_load=0",
"tooldata_last_index=9",
"tool_1.toolno=-1",
"tool_2.toolno=-1",
"tool_pocket_7.toolno=2",
"tool_pocket_7.pocketno=7",
"tool_pocket_7.z=3.125",
"tool_pocket_7.diameter=1.5",
"tool_pocket_7.frontangle=12",
"tool_pocket_7.backangle=34",
"tool_pocket_7.orientation=4",
"tool_pocket_7.comment=random finish tool",
"tool_pocket_9.toolno=5",
"tool_pocket_9.pocketno=9",
"tool_pocket_9.z=3",
"tool_pocket_9.comment=random rough tool",
"tool_index_for_tool_2=7",
].join("\n"),
);
verifyExpectedOutput(
"save_tool_table_random",
interp.saveToolTable(randomToolTablePath),
[
"tooldata_save=0",
"tool_pocket_7.toolno=2",
"tool_pocket_9.toolno=5",
].join("\n"),
);
const savedRandomToolTable = interp.readTextFile(randomToolTablePath);
assert.equal(savedRandomToolTable.includes("T2"), true);
assert.equal(savedRandomToolTable.includes("P7"), true);
assert.equal(savedRandomToolTable.includes(";random finish tool"), true);
assert.equal(savedRandomToolTable.includes("T5"), true);
assert.equal(savedRandomToolTable.includes("P9"), true);
assert.equal(savedRandomToolTable.includes(";random rough tool"), true);
console.log("interp_wasm_node_smoke=ok");