From 6cf7c73392dda24a034bb132d363806979b01783 Mon Sep 17 00:00:00 2001 From: cnc Date: Wed, 3 Jun 2026 10:58:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=82=E8=80=83=E6=89=80=E6=9C=89=E5=88=86?= =?UTF-8?q?=E7=BB=84=EF=BC=9A=E5=AE=8C=E5=96=84OPFS=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=A4=87=E4=BB=BD=E6=A1=A5=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 结论:按 LinuxCNC rs274ngc_pre.cc save_parameters()/restore_parameters() 语义,补齐浏览器 OPFS 参数文件 .bak 持久化;未扩展 smoke 解析行为。 验证:./test-native.sh;./test-linuxcnc-source-link.sh;./test-web-wasm-node-smoke.sh;./test-web-wasm-browser-smoke.sh。 --- web/src/wasm-core.js | 11 ++- web/test-browser-wasm-smoke-app-sections.js | 18 +++- ...wser-wasm-smoke-opfs-parameter-sections.js | 84 +++++++++++++------ 3 files changed, 86 insertions(+), 27 deletions(-) diff --git a/web/src/wasm-core.js b/web/src/wasm-core.js index b2ba64d..339cf3d 100644 --- a/web/src/wasm-core.js +++ b/web/src/wasm-core.js @@ -703,10 +703,19 @@ export async function createWasmSimulator(moduleOptions = {}) { throw new Error("OPFS workspace is not available for parameter file persistence"); } cleanupLinuxCncParameterFiles(module); - await opfs.loadParameterFile(parameterPath); + const previousParameterFile = await opfs.loadParameterFile(parameterPath); try { const events = parseText(program, dialect, options, { keepLinuxCncParameterFiles: true }); await opfs.persistParameterFile(parameterPath); + // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() + // links the previous parameter file to filename + ".bak" before + // replacing filename. Browser filesystems may not expose that link + // through the WASM mirror, so preserve the loaded OPFS file here. + if (previousParameterFile === null) { + await opfs.removeFile(`${parameterPath}.bak`); + } else if (!(await opfs.exists(`${parameterPath}.bak`))) { + await opfs.writeFile(`${parameterPath}.bak`, previousParameterFile); + } return events; } finally { cleanupLinuxCncParameterFiles(module); diff --git a/web/test-browser-wasm-smoke-app-sections.js b/web/test-browser-wasm-smoke-app-sections.js index 1a9569e..903b5b6 100644 --- a/web/test-browser-wasm-smoke-app-sections.js +++ b/web/test-browser-wasm-smoke-app-sections.js @@ -28,6 +28,20 @@ export async function runBrowserAppOpfsSections(context) { const savedParameterFile = new TextDecoder().decode(await appSimulator.fs.opfs.readFile("parameters/rs274ngc.var")); expectIncludes(savedParameterFile, "5161\t1.000000", "app frame did not persist LinuxCNC parameter state into OPFS"); + await appSimulator.fs.opfs.removeFile("parameters/rs274ngc.var.bak"); + parseAppFrameProgram(appFrame, "G21 G90\nG0 X2\nG28.1\nM30\n"); + await waitFor( + () => appFrame.contentDocument?.querySelector("#axisX")?.value === "2.000", + "app frame did not parse second program for parameter backup coverage", + ); + // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() + // preserves the previous parameter file as filename + ".bak". + const updatedParameterFile = new TextDecoder().decode(await appSimulator.fs.opfs.readFile("parameters/rs274ngc.var")); + expectIncludes(updatedParameterFile, "5161\t2.000000", + "app frame did not replace LinuxCNC parameter state after second parse"); + const savedParameterBackup = new TextDecoder().decode(await appSimulator.fs.opfs.readFile("parameters/rs274ngc.var.bak")); + expectIncludes(savedParameterBackup, "5161\t1.000000", + "app frame did not persist LinuxCNC parameter backup into OPFS"); } finally { await disposeAppFrame(appFrame); } @@ -38,7 +52,7 @@ export async function runBrowserAppOpfsSections(context) { await waitForAppControls(restoredAppFrame, "restored app frame did not load CNC controls"); await waitFor(() => { const restoredInput = restoredAppFrame.contentDocument?.querySelector("#programInput"); - return restoredInput?.value === "G21 G90\nG0 X1\nG28.1\nM30\n"; + return restoredInput?.value === "G21 G90\nG0 X2\nG28.1\nM30\n"; }, "app frame did not restore current program from OPFS after reload"); await waitForAppAlarm( restoredAppFrame, @@ -48,7 +62,7 @@ export async function runBrowserAppOpfsSections(context) { await getAppFrameSimulator(restoredAppFrame, "restored app frame did not create OPFS simulator workspace"); parseAppFrameProgram( restoredAppFrame, - "G21 G90\nF100\nO10 if [#5161 EQ 1]\nG1 X12\nO10 endif\nM30\n", + "G21 G90\nF100\nO10 if [#5161 EQ 2]\nG1 X12\nO10 endif\nM30\n", ); await waitForAppAlarm( restoredAppFrame, diff --git a/web/test-browser-wasm-smoke-opfs-parameter-sections.js b/web/test-browser-wasm-smoke-opfs-parameter-sections.js index 5947b27..1d99974 100644 --- a/web/test-browser-wasm-smoke-opfs-parameter-sections.js +++ b/web/test-browser-wasm-smoke-opfs-parameter-sections.js @@ -9,6 +9,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) { expectText, expectWasmFilesMissing, near, + readOpfsText, removeWasmPathIfExists, runSection, withSmokeSimulator, @@ -21,9 +22,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) { "linuxcnc", { backend: "linuxcnc-rs274" }, ); - const opfsParameterFile = new TextDecoder().decode( - await simulator.fs.opfs.readFile("parameters/rs274ngc.var"), - ); + const opfsParameterFile = await readOpfsText(simulator, "parameters/rs274ngc.var"); expectIncludes(opfsParameterFile, "5161\t1.000000", "LinuxCNC parameter file was not persisted into OPFS"); }); @@ -42,6 +41,20 @@ export async function runBrowserOpfsParameterSections(context, simulator) { }); }); + await runSection("opfs default parameter backup", async () => { + // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() + // preserves the previous default parameter file as filename + ".bak". + await simulator.parseWithParameterFile( + "G21 G90\nM30\n", + "parameters/rs274ngc.var", + "linuxcnc", + { backend: "linuxcnc-rs274" }, + ); + const defaultParameterBackup = await readOpfsText(simulator, "parameters/rs274ngc.var.bak"); + expectIncludes(defaultParameterBackup, "5161\t1.000000", + "LinuxCNC default OPFS parameter backup did not preserve the previous file"); + }); + await runSection("opfs parameter temporary cleanup", async () => { await simulator.fs.opfs.writeFile("parameters/rs274ngc.var.new", "stale temporary parameter file\n"); // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() writes @@ -73,24 +86,49 @@ export async function runBrowserOpfsParameterSections(context, simulator) { simulator.fs.module.FS.writeFile("rs274ngc.var", "5161\t2.000000\n"); simulator.fs.module.FS.writeFile("rs274ngc.var.bak", "5161\t1.000000\n"); await simulator.fs.opfs.persistParameterFile("parameters/backup-bridge.var"); - const opfsParameterBackup = new TextDecoder().decode( - await simulator.fs.opfs.readFile("parameters/backup-bridge.var.bak"), - ); + const opfsParameterBackup = await readOpfsText(simulator, "parameters/backup-bridge.var.bak"); expectIncludes(opfsParameterBackup, "5161\t1.000000", "LinuxCNC parameter backup file was not bridged into OPFS"); }); + await runSection("opfs parameter backup after existing save", async () => { + await simulator.fs.opfs.writeFile("parameters/existing-save.var", "5161\t3.000000\n"); + // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() + // unlinks filename + ".bak", links the current parameter file to + // filename + ".bak", then renames filename + ".new" over filename. + await simulator.parseWithParameterFile( + "G21 G90\nG0 X4\nG28.1\nM30\n", + "parameters/existing-save.var", + "linuxcnc", + { backend: "linuxcnc-rs274" }, + ); + const existingSaveParameterFile = await readOpfsText(simulator, "parameters/existing-save.var"); + expectIncludes(existingSaveParameterFile, "5161\t4.000000", + "LinuxCNC existing OPFS parameter file was not replaced after save"); + const existingSaveParameterBackup = await readOpfsText(simulator, "parameters/existing-save.var.bak"); + expectIncludes(existingSaveParameterBackup, "5161\t3.000000", + "LinuxCNC existing OPFS parameter backup did not preserve the previous file"); + await expectMissingOpfsFile( + () => simulator.fs.opfs.readFile("parameters/existing-save.var.new"), + "LinuxCNC existing OPFS parameter temporary file remained after save", + ); + }); + await runSection("opfs parameter stale backup cleanup", async () => { + await simulator.fs.opfs.removeFile("parameters/stale-backup-cleanup.var"); + await simulator.fs.opfs.removeFile("parameters/stale-backup-cleanup.var.new"); + await simulator.fs.opfs.removeFile("parameters/stale-backup-cleanup.var.bak"); + await simulator.fs.opfs.writeFile("parameters/stale-backup-cleanup.var.bak", "stale backup without main file\n"); removeWasmPathIfExists(simulator, "rs274ngc.var.bak"); removeWasmPathIfExists(simulator, "/rs274ngc.var.bak"); await simulator.parseWithParameterFile( "G21 G90\nM30\n", - "parameters/rs274ngc.var", + "parameters/stale-backup-cleanup.var", "linuxcnc", { backend: "linuxcnc-rs274" }, ); await expectMissingOpfsFile( - () => simulator.fs.opfs.readFile("parameters/rs274ngc.var.bak"), + () => simulator.fs.opfs.readFile("parameters/stale-backup-cleanup.var.bak"), "stale LinuxCNC parameter backup file remained in OPFS", ); // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() @@ -111,6 +149,9 @@ export async function runBrowserOpfsParameterSections(context, simulator) { }); await runSection("opfs missing parameter file creation", async () => { + await simulator.fs.opfs.removeFile("parameters/missing-main.var"); + await simulator.fs.opfs.removeFile("parameters/missing-main.var.new"); + await simulator.fs.opfs.removeFile("parameters/missing-main.var.bak"); await simulator.fs.opfs.writeFile("parameters/missing-main.var.bak", "stale backup without main file\n"); await simulator.fs.opfs.writeFile("parameters/missing-main.var.new", "stale temporary without main file\n"); // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() unlinks @@ -124,13 +165,15 @@ export async function runBrowserOpfsParameterSections(context, simulator) { "linuxcnc", { backend: "linuxcnc-rs274" }, ); - const missingMainParameterFile = new TextDecoder().decode( - await simulator.fs.opfs.readFile("parameters/missing-main.var"), - ); + const missingMainParameterFile = await readOpfsText(simulator, "parameters/missing-main.var"); expectIncludes(missingMainParameterFile, "5220\t1.000000", "LinuxCNC missing parameter file was not created through OPFS"); expectIncludes(missingMainParameterFile, "5161\t0.000000", "LinuxCNC missing parameter file did not include required G28 parameter"); + await expectMissingOpfsFile( + () => simulator.fs.opfs.readFile("parameters/missing-main.var.bak"), + "stale LinuxCNC missing-parameter backup remained in OPFS after first creation", + ); const missingMainRestoreEvents = await simulator.parseWithParameterFile( "G21 G90\nF100\nO10 if [#5220 EQ 1]\nG1 X6\nO10 endif\n", "parameters/missing-main.var", @@ -139,10 +182,9 @@ export async function runBrowserOpfsParameterSections(context, simulator) { ); expectEvent(missingMainRestoreEvents, (event) => event.type === "linear-feed" && near(event.end.x, 6), "LinuxCNC-created missing OPFS parameter file was not restored on the next parse"); - await expectMissingOpfsFile( - () => simulator.fs.opfs.readFile("parameters/missing-main.var.bak"), - "stale LinuxCNC missing-parameter backup remained in OPFS", - ); + const missingMainParameterBackup = await readOpfsText(simulator, "parameters/missing-main.var.bak"); + expectIncludes(missingMainParameterBackup, "5220\t1.000000", + "LinuxCNC-created OPFS parameter file was not backed up on the next successful save"); expectMissingWasmPath( () => simulator.fs.module.FS.readFile("rs274ngc.var.bak"), "stale LinuxCNC missing-parameter backup remained in WASM FS", @@ -169,9 +211,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) { "Parameter file out of order", "LinuxCNC accepted an out-of-order OPFS parameter file", ); - const outOfOrderParameterFile = new TextDecoder().decode( - await simulator.fs.opfs.readFile("parameters/out-of-order.var"), - ); + const outOfOrderParameterFile = await readOpfsText(simulator, "parameters/out-of-order.var"); expectText(outOfOrderParameterFile, "5162\t1.000000\n5161\t1.000000\n", "LinuxCNC persisted an out-of-order OPFS parameter file after restore failure"); expectMissingWasmPath( @@ -189,9 +229,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) { "Parameter number out of range", "LinuxCNC accepted an out-of-range OPFS parameter file", ); - const outOfRangeParameterFile = new TextDecoder().decode( - await simulator.fs.opfs.readFile("parameters/out-of-range.var"), - ); + const outOfRangeParameterFile = await readOpfsText(simulator, "parameters/out-of-range.var"); expectText(outOfRangeParameterFile, "0\t1.000000\n", "LinuxCNC persisted an out-of-range OPFS parameter file after restore failure"); expectMissingWasmPath( @@ -209,9 +247,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) { "", "LinuxCNC accepted a failing parse that should not persist parameters", ); - const failedParseParameterFile = new TextDecoder().decode( - await simulator.fs.opfs.readFile("parameters/failed-parse.var"), - ); + const failedParseParameterFile = await readOpfsText(simulator, "parameters/failed-parse.var"); if (failedParseParameterFile.includes("5161\t1.000000")) { throw new Error("LinuxCNC persisted failed parse parameter updates into OPFS"); }