按规划继续工作

结论:补齐 OPFS 会话快照自定义文件名与非法快照文件名拒绝验证,覆盖 Node OPFS 与 Chromium OPFS 路径,并通过 host/WASM/browser 聚合验证。
This commit is contained in:
2026-06-08 09:21:00 +08:00
parent 8c7c2f1079
commit 78f54bb64c
5 changed files with 93 additions and 15 deletions

View File

@@ -100,9 +100,9 @@ paths, unavailable OPFS storage, and the host-side OPFS path model for INI,
tool table, parameter, G-code, preview-cache, and session-snapshot storage tool table, parameter, G-code, preview-cache, and session-snapshot storage
targets. It also validates the host-side session snapshot JSON envelope and targets. It also validates the host-side session snapshot JSON envelope and
round-trip store, including unsupported format/version, session-id mismatch, round-trip store, including unsupported format/version, session-id mismatch,
and non-object metadata/payload rejection, plus pure-text machine file and non-object metadata/payload rejection, custom snapshot filenames, and invalid
G-code stores without defining CNC machine-state or file-format semantics. It snapshot filename rejection, plus pure-text machine file and G-code stores
now also validates the without defining CNC machine-state or file-format semantics. It now also validates the
OPFS-to-WASM parameter-file bridge with a mock interpreter SDK to ensure the OPFS-to-WASM parameter-file bridge with a mock interpreter SDK to ensure the
host boundary copies text into and out of the WASM filesystem without defining host boundary copies text into and out of the WASM filesystem without defining
parameter semantics, and that INI-derived machine file names are still parameter semantics, and that INI-derived machine file names are still
@@ -114,8 +114,8 @@ keeping host override policy outside CNC semantics.
The browser INI/OPFS smoke script serves `wasm-port/` over localhost and runs The browser INI/OPFS smoke script serves `wasm-port/` over localhost and runs
Chromium headless against a test page that imports the JS SDK, loads the INI Chromium headless against a test page that imports the JS SDK, loads the INI
WASM module, queries vendored LinuxCNC INI parsing through the SDK, and WASM module, queries vendored LinuxCNC INI parsing through the SDK, and
performs an OPFS text-file, generic session snapshot, invalid snapshot performs an OPFS text-file, generic session snapshot, custom snapshot filename,
envelope, machine file, and G-code text round trip. invalid snapshot filename/envelope, machine file, and G-code text round trip.
The browser interpreter smoke script serves `wasm-port/` over localhost and The browser interpreter smoke script serves `wasm-port/` over localhost and
runs Chromium headless against a test page that loads the interpreter-core runs Chromium headless against a test page that loads the interpreter-core
@@ -186,8 +186,8 @@ The validation fails if:
| --- | --- | | --- | --- |
| `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI. | | `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI. |
| `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. | | `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. |
| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including envelope rejection paths, machine file store, G-code text store, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. | | `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including custom filenames and envelope/path rejection paths, machine file store, G-code text store, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. |
| `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, INI/interpreter WASM module loading, OPFS text-file round trip, generic session snapshot round trip plus envelope rejection paths, machine file text round trip, G-code text round trip, and the INI panel UI's machine-session load, G-code run, and canonical-event display paths in a real browser runtime. | | `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, INI/interpreter WASM module loading, OPFS text-file round trip, generic session snapshot round trip plus custom filename and envelope/path rejection paths, machine file text round trip, G-code text round trip, and the INI panel UI's machine-session load, G-code run, and canonical-event display paths in a real browser runtime. |
| `tests/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI, including OPFS-backed parameter-file restore/save and tool-table load/save through vendored LinuxCNC source. | | `tests/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI, including OPFS-backed parameter-file restore/save and tool-table load/save through vendored LinuxCNC source. |
| `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. | | `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. |
@@ -270,10 +270,11 @@ derived from vendored LinuxCNC INI boolean parsing, INI-derived
`[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` file names mapped to OPFS `[RS274NGC]PARAMETER_FILE` and `[EMCIO]TOOL_TABLE` file names mapped to OPFS
machine files, OPFS path-model rejection of invalid INI-derived file names, machine files, OPFS path-model rejection of invalid INI-derived file names,
explicit host session file-name overrides taking precedence over INI-derived explicit host session file-name overrides taking precedence over INI-derived
names, session snapshot envelope rejection for unsupported format/version and names, session snapshot custom filename handling plus envelope/path rejection
wrong session id, and a browser interpreter smoke that uses the same session for unsupported format/version, wrong session id, and invalid snapshot
bridge with real LinuxCNC INI WASM parsing before saving OPFS-backed parameter filenames, and a browser interpreter smoke that uses the same session bridge
and tool-table text through vendored LinuxCNC file APIs. with real LinuxCNC INI WASM parsing before saving OPFS-backed parameter and
tool-table text through vendored LinuxCNC file APIs.
Full browser coverage, full SDK coverage, and richer machine-state validation Full browser coverage, full SDK coverage, and richer machine-state validation
remain future work. remain future work.

View File

@@ -71,8 +71,8 @@ semantic rewrites:
file and G-code storage, parameter/tool-table bridge copying into the file and G-code storage, parameter/tool-table bridge copying into the
interpreter SDK filesystem, grouped machine-session loading, explicit interpreter SDK filesystem, grouped machine-session loading, explicit
session file-name overrides, invalid INI-derived machine file names, generic session file-name overrides, invalid INI-derived machine file names, generic
session snapshot envelope rejection paths, and a Chromium localhost round session snapshot custom filenames plus envelope/path rejection paths, and a
trip for those persistence paths. Chromium localhost round trip for those persistence paths.
- Host-side smoke validation is aggregated by - Host-side smoke validation is aggregated by
`tests/host/verify_host_smokes.sh` so Node, WASM, OPFS, and browser checks `tests/host/verify_host_smokes.sh` so Node, WASM, OPFS, and browser checks
run from one command. run from one command.

View File

@@ -83,8 +83,9 @@ Current validation is intentionally mechanical:
`runtime/opfs/file-service.js` adapter. `runtime/opfs/path-model.js` now `runtime/opfs/file-service.js` adapter. `runtime/opfs/path-model.js` now
defines paths for INI, tool table, parameter file, G-code program, defines paths for INI, tool table, parameter file, G-code program,
preview-cache, and session-snapshot targets. `runtime/opfs/snapshot-store.js` preview-cache, and session-snapshot targets. `runtime/opfs/snapshot-store.js`
adds a generic JSON session snapshot envelope and validates its format, adds a generic JSON session snapshot envelope, supports custom snapshot
version, session id, metadata, and payload shape. filenames under the session directory, and validates its format, version,
session id, metadata, payload shape, and filename boundary.
`runtime/opfs/machine-file-store.js` adds pure-text storage for INI, tool `runtime/opfs/machine-file-store.js` adds pure-text storage for INI, tool
table, parameter file, and G-code program content. table, parameter file, and G-code program content.
`runtime/opfs/linuxcnc-parameter-bridge.js` copies OPFS-backed parameter `runtime/opfs/linuxcnc-parameter-bridge.js` copies OPFS-backed parameter

View File

@@ -116,6 +116,43 @@ JOINTS = 3
assertEqual(loadedSnapshot.createdAt, savedSnapshot.createdAt, "snapshot timestamp"); assertEqual(loadedSnapshot.createdAt, savedSnapshot.createdAt, "snapshot timestamp");
assertEqual(loadedSnapshot.payload.files.ini, opfsPath, "snapshot payload"); assertEqual(loadedSnapshot.payload.files.ini, opfsPath, "snapshot payload");
assertEqual(loadedSnapshot.metadata.source, "browser-smoke", "snapshot metadata"); assertEqual(loadedSnapshot.metadata.source, "browser-smoke", "snapshot metadata");
assertEqual(
sessionSnapshotPath("browser-session", "browser-custom-snapshot.json"),
"linuxcnc/sessions/browser-session/browser-custom-snapshot.json",
"snapshot custom path",
);
const customSnapshot = await saveSessionSnapshot(
"browser-session",
snapshotPayload,
{
filename: "browser-custom-snapshot.json",
createdAt: "2026-06-08T00:00:00.000Z",
metadata: { source: "browser-smoke-custom" },
},
);
const loadedCustomSnapshot = await loadSessionSnapshot(
"browser-session",
{ filename: "browser-custom-snapshot.json" },
);
assertEqual(
loadedCustomSnapshot.metadata.source,
customSnapshot.metadata.source,
"custom snapshot metadata",
);
await assertRejects(
"browser snapshot invalid filename save",
() => saveSessionSnapshot("browser-session", {}, {
filename: "nested/snapshot.json",
}),
/Invalid session snapshot filename/,
);
await assertRejects(
"browser snapshot invalid filename load",
() => loadSessionSnapshot("browser-session", {
filename: "../snapshot.json",
}),
/Invalid session snapshot filename/,
);
await saveTextFile( await saveTextFile(
sessionSnapshotPath("browser-bad-format"), sessionSnapshotPath("browser-bad-format"),
JSON.stringify({ JSON.stringify({

View File

@@ -104,6 +104,10 @@ assert.equal(parameterFilePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/linuxc
assert.equal(gcodeProgramPath("fixture.ngc"), "linuxcnc/gcode/fixture.ngc"); assert.equal(gcodeProgramPath("fixture.ngc"), "linuxcnc/gcode/fixture.ngc");
assert.equal(previewCachePath("fixture"), "linuxcnc/preview-cache/fixture/preview.json"); assert.equal(previewCachePath("fixture"), "linuxcnc/preview-cache/fixture/preview.json");
assert.equal(sessionSnapshotPath("session-1"), "linuxcnc/sessions/session-1/snapshot.json"); assert.equal(sessionSnapshotPath("session-1"), "linuxcnc/sessions/session-1/snapshot.json");
assert.equal(
sessionSnapshotPath("session-1", "custom-snapshot.json"),
"linuxcnc/sessions/session-1/custom-snapshot.json",
);
assert.deepEqual(defaultMachinePaths("xyzab-tdr"), { assert.deepEqual(defaultMachinePaths("xyzab-tdr"), {
ini: "linuxcnc/machines/xyzab-tdr/machine.ini", ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl", toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
@@ -167,6 +171,23 @@ await saveSessionSnapshot("session-1", snapshotPayload, {
metadata: { source: "node-smoke" }, metadata: { source: "node-smoke" },
}); });
assert.deepEqual(await loadSessionSnapshot("session-1", { storage }), snapshot); assert.deepEqual(await loadSessionSnapshot("session-1", { storage }), snapshot);
const customSnapshot = await saveSessionSnapshot("session-1", snapshotPayload, {
storage,
filename: "custom-snapshot.json",
createdAt: "2026-06-08T00:00:00.000Z",
metadata: { source: "node-smoke-custom" },
});
assert.equal(
customSnapshot.metadata.source,
"node-smoke-custom",
);
assert.deepEqual(
await loadSessionSnapshot("session-1", {
storage,
filename: "custom-snapshot.json",
}),
customSnapshot,
);
await saveMachineTextFiles("xyzab-tdr", { await saveMachineTextFiles("xyzab-tdr", {
ini: "[EMC]\nMACHINE = xyzab-tdr\n", ini: "[EMC]\nMACHINE = xyzab-tdr\n",
@@ -569,6 +590,24 @@ assert.throws(
() => machineIniPath("../escape"), () => machineIniPath("../escape"),
/Invalid machine id/, /Invalid machine id/,
); );
assert.throws(
() => sessionSnapshotPath("session-1", "nested/snapshot.json"),
/Invalid session snapshot filename/,
);
await assert.rejects(
() => saveSessionSnapshot("session-1", {}, {
storage,
filename: "nested/snapshot.json",
}),
/Invalid session snapshot filename/,
);
await assert.rejects(
() => loadSessionSnapshot("session-1", {
storage,
filename: "../snapshot.json",
}),
/Invalid session snapshot filename/,
);
await assert.rejects( await assert.rejects(
() => saveMachineTextFiles("xyzab-tdr", null, { storage }), () => saveMachineTextFiles("xyzab-tdr", null, { storage }),
/machine files must be a plain object/, /machine files must be a plain object/,