按规划继续工作

结论:补齐通用会话快照 envelope 的格式、版本、会话 ID 与对象形状拒绝验证,覆盖 Node OPFS 与 Chromium OPFS 路径,并通过 host/WASM/browser 聚合验证。
This commit is contained in:
2026-06-08 09:16:24 +08:00
parent de55c26532
commit 8c7c2f1079
5 changed files with 115 additions and 13 deletions

View File

@@ -99,8 +99,10 @@ directory creation, text save/load, missing file behavior, invalid relative
paths, unavailable OPFS storage, and the host-side OPFS path model for INI, 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 plus pure-text machine file and G-code stores without round-trip store, including unsupported format/version, session-id mismatch,
defining CNC machine-state or file-format semantics. It now also validates the and non-object metadata/payload rejection, plus pure-text machine file and
G-code stores 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
@@ -112,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, machine file, and G-code performs an OPFS text-file, generic session snapshot, invalid snapshot
text round trip. 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
@@ -184,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, 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 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/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, 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 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_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. |
@@ -268,9 +270,10 @@ 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, and a browser interpreter smoke that uses the same session bridge with names, session snapshot envelope rejection for unsupported format/version and
real LinuxCNC INI WASM parsing before saving OPFS-backed parameter and wrong session id, and a browser interpreter smoke that uses the same session
tool-table text through vendored LinuxCNC file APIs. bridge 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

@@ -70,8 +70,9 @@ semantic rewrites:
host-side path model, generic session snapshot storage, pure-text machine host-side path model, generic session snapshot storage, pure-text machine
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, and a session file-name overrides, invalid INI-derived machine file names, generic
Chromium localhost round trip for those persistence paths. session snapshot envelope rejection paths, and a 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,7 +83,8 @@ 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. adds a generic JSON session snapshot envelope and validates its format,
version, session id, metadata, and payload shape.
`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

@@ -9,7 +9,7 @@
<script type="module"> <script type="module">
import { createLinuxCncIniSdk } from "../../runtime/sdk/src/index.js"; import { createLinuxCncIniSdk } from "../../runtime/sdk/src/index.js";
import { loadTextFile, saveTextFile } from "../../runtime/opfs/file-service.js"; import { loadTextFile, saveTextFile } from "../../runtime/opfs/file-service.js";
import { machineIniPath } from "../../runtime/opfs/path-model.js"; import { machineIniPath, sessionSnapshotPath } from "../../runtime/opfs/path-model.js";
import { import {
loadSessionSnapshot, loadSessionSnapshot,
saveSessionSnapshot, saveSessionSnapshot,
@@ -29,6 +29,18 @@
} }
} }
async function assertRejects(label, operation, expectedPattern) {
try {
await operation();
} catch (error) {
if (!expectedPattern.test(error.message)) {
throw new Error(`${label}: unexpected error ${error.message}`);
}
return;
}
throw new Error(`${label}: expected rejection`);
}
function delay(ms) { function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
@@ -104,6 +116,44 @@ 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");
await saveTextFile(
sessionSnapshotPath("browser-bad-format"),
JSON.stringify({
...loadedSnapshot,
format: "other-format",
sessionId: "browser-bad-format",
}),
);
await assertRejects(
"browser snapshot bad format",
() => loadSessionSnapshot("browser-bad-format"),
/Unsupported session snapshot format/,
);
await saveTextFile(
sessionSnapshotPath("browser-bad-version"),
JSON.stringify({
...loadedSnapshot,
version: 99,
sessionId: "browser-bad-version",
}),
);
await assertRejects(
"browser snapshot bad version",
() => loadSessionSnapshot("browser-bad-version"),
/Unsupported session snapshot version/,
);
await saveTextFile(
sessionSnapshotPath("browser-bad-session-id"),
JSON.stringify({
...loadedSnapshot,
sessionId: "other-browser-session",
}),
);
await assertRejects(
"browser snapshot id mismatch",
() => loadSessionSnapshot("browser-bad-session-id"),
/Session snapshot id mismatch/,
);
await saveMachineTextFiles("browser-smoke", { await saveMachineTextFiles("browser-smoke", {
ini: iniText, ini: iniText,

View File

@@ -130,6 +130,26 @@ assert.deepEqual(snapshot, {
payload: snapshotPayload, payload: snapshotPayload,
}); });
assert.equal(validateSessionSnapshot(snapshot, "session-1"), snapshot); assert.equal(validateSessionSnapshot(snapshot, "session-1"), snapshot);
assert.throws(
() => validateSessionSnapshot({ ...snapshot, format: "other-format" }, "session-1"),
/Unsupported session snapshot format/,
);
assert.throws(
() => validateSessionSnapshot({ ...snapshot, version: 99 }, "session-1"),
/Unsupported session snapshot version/,
);
assert.throws(
() => validateSessionSnapshot({ ...snapshot, sessionId: "other-session" }, "session-1"),
/Session snapshot id mismatch/,
);
assert.throws(
() => validateSessionSnapshot({ ...snapshot, metadata: null }, "session-1"),
/snapshot metadata must be a plain object/,
);
assert.throws(
() => validateSessionSnapshot({ ...snapshot, payload: [] }, "session-1"),
/snapshot payload must be a plain object/,
);
await saveTextFile( await saveTextFile(
"linuxcnc/machines/xyzab.ini", "linuxcnc/machines/xyzab.ini",
@@ -558,6 +578,33 @@ await assert.rejects(
() => loadSessionSnapshot("bad-json", { storage }), () => loadSessionSnapshot("bad-json", { storage }),
/Invalid session snapshot JSON/, /Invalid session snapshot JSON/,
); );
await saveTextFile(
sessionSnapshotPath("bad-format"),
JSON.stringify({ ...snapshot, sessionId: "bad-format", format: "other-format" }),
storage,
);
await assert.rejects(
() => loadSessionSnapshot("bad-format", { storage }),
/Unsupported session snapshot format/,
);
await saveTextFile(
sessionSnapshotPath("bad-version"),
JSON.stringify({ ...snapshot, sessionId: "bad-version", version: 99 }),
storage,
);
await assert.rejects(
() => loadSessionSnapshot("bad-version", { storage }),
/Unsupported session snapshot version/,
);
await saveTextFile(
sessionSnapshotPath("bad-session-id"),
JSON.stringify({ ...snapshot, sessionId: "other-session" }),
storage,
);
await assert.rejects(
() => loadSessionSnapshot("bad-session-id", { storage }),
/Session snapshot id mismatch/,
);
await assert.rejects( await assert.rejects(
() => loadSessionSnapshot("missing-session", { storage }), () => loadSessionSnapshot("missing-session", { storage }),
/missing directory|missing file/, /missing directory|missing file/,