diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index bc7eaf6..2240f2c 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -93,7 +93,10 @@ Emscripten filesystem through the SDK and runs them through LinuxCNC file execution path. The same Node smoke writes LinuxCNC-format parameter files into the Emscripten filesystem and validates vendored `Interp::restore_parameters()` and `Interp::save_parameters()`, including the -saved parameter values and `.bak` backup file boundary. +saved parameter values and `.bak` backup file boundary. It also writes the +negative G-code fixtures into the Emscripten filesystem and verifies their +LinuxCNC-produced error text through the `Interp::open()`/`read()`/`execute()` +file path. The OPFS host-boundary script validates the JavaScript file-service adapter with a Node mock of the browser File System Access handles. It covers nested @@ -260,6 +263,9 @@ path additionally covers the same canonical-event fixture group, plus `position_params` uses a dedicated file-path expectation under `tests/fixtures/canon_file/` because LinuxCNC file execution advances the post-execute position parameters differently than the line-by-line MDI smoke. +The same Node WASM file-path smoke also covers the negative fixture group and +checks the LinuxCNC file-execution error text plus absent canonical motion +constraints where applicable. The Node WASM interpreter smoke also covers LinuxCNC parameter-file restore/save behavior through the exported C ABI, including out-of-order file rejection, missing-file success, required numeric parameter writeback, removal @@ -356,6 +362,19 @@ Node WASM file-path coverage currently includes: - `position_params` through the dedicated `canon_file/` expectation - `namedparam_semantics` through the INI-aware file ABI +Node WASM file-path negative coverage currently includes every fixture under +`tests/fixtures/gcode_errors/`: + +- `g1_zero_feed` +- `arc_radius_mismatch` +- `arc_zero_radius` +- `cutter_comp_plane_change` +- `g53_incremental` +- `namedparam_readonly` +- `numbered_param_readonly` +- `tool_length_offset_not_found` +- `tool_not_found` + Browser interpreter `Interp::execute()` coverage currently includes: - `minimal_linear` diff --git a/wasm-port/tests/wasm/node/verify_interp_wasm.mjs b/wasm-port/tests/wasm/node/verify_interp_wasm.mjs index 62b1e8e..0895230 100644 --- a/wasm-port/tests/wasm/node/verify_interp_wasm.mjs +++ b/wasm-port/tests/wasm/node/verify_interp_wasm.mjs @@ -34,6 +34,45 @@ function verifyExpectedOutput(fixtureName, output, expectedText) { } } +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, "../../.."); @@ -92,6 +131,21 @@ for (const fixtureName of INTERP_ERROR_FIXTURES) { 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(