参考所有分组:完善OPFS参数备份桥接

结论:按 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。
This commit is contained in:
cnc
2026-06-03 10:58:05 +08:00
parent 028825cd9c
commit 6cf7c73392
3 changed files with 86 additions and 27 deletions

View File

@@ -703,10 +703,19 @@ export async function createWasmSimulator(moduleOptions = {}) {
throw new Error("OPFS workspace is not available for parameter file persistence"); throw new Error("OPFS workspace is not available for parameter file persistence");
} }
cleanupLinuxCncParameterFiles(module); cleanupLinuxCncParameterFiles(module);
await opfs.loadParameterFile(parameterPath); const previousParameterFile = await opfs.loadParameterFile(parameterPath);
try { try {
const events = parseText(program, dialect, options, { keepLinuxCncParameterFiles: true }); const events = parseText(program, dialect, options, { keepLinuxCncParameterFiles: true });
await opfs.persistParameterFile(parameterPath); 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; return events;
} finally { } finally {
cleanupLinuxCncParameterFiles(module); cleanupLinuxCncParameterFiles(module);

View File

@@ -28,6 +28,20 @@ export async function runBrowserAppOpfsSections(context) {
const savedParameterFile = new TextDecoder().decode(await appSimulator.fs.opfs.readFile("parameters/rs274ngc.var")); const savedParameterFile = new TextDecoder().decode(await appSimulator.fs.opfs.readFile("parameters/rs274ngc.var"));
expectIncludes(savedParameterFile, "5161\t1.000000", expectIncludes(savedParameterFile, "5161\t1.000000",
"app frame did not persist LinuxCNC parameter state into OPFS"); "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 { } finally {
await disposeAppFrame(appFrame); await disposeAppFrame(appFrame);
} }
@@ -38,7 +52,7 @@ export async function runBrowserAppOpfsSections(context) {
await waitForAppControls(restoredAppFrame, "restored app frame did not load CNC controls"); await waitForAppControls(restoredAppFrame, "restored app frame did not load CNC controls");
await waitFor(() => { await waitFor(() => {
const restoredInput = restoredAppFrame.contentDocument?.querySelector("#programInput"); 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"); }, "app frame did not restore current program from OPFS after reload");
await waitForAppAlarm( await waitForAppAlarm(
restoredAppFrame, restoredAppFrame,
@@ -48,7 +62,7 @@ export async function runBrowserAppOpfsSections(context) {
await getAppFrameSimulator(restoredAppFrame, "restored app frame did not create OPFS simulator workspace"); await getAppFrameSimulator(restoredAppFrame, "restored app frame did not create OPFS simulator workspace");
parseAppFrameProgram( parseAppFrameProgram(
restoredAppFrame, 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( await waitForAppAlarm(
restoredAppFrame, restoredAppFrame,

View File

@@ -9,6 +9,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) {
expectText, expectText,
expectWasmFilesMissing, expectWasmFilesMissing,
near, near,
readOpfsText,
removeWasmPathIfExists, removeWasmPathIfExists,
runSection, runSection,
withSmokeSimulator, withSmokeSimulator,
@@ -21,9 +22,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) {
"linuxcnc", "linuxcnc",
{ backend: "linuxcnc-rs274" }, { backend: "linuxcnc-rs274" },
); );
const opfsParameterFile = new TextDecoder().decode( const opfsParameterFile = await readOpfsText(simulator, "parameters/rs274ngc.var");
await simulator.fs.opfs.readFile("parameters/rs274ngc.var"),
);
expectIncludes(opfsParameterFile, "5161\t1.000000", "LinuxCNC parameter file was not persisted into OPFS"); 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 runSection("opfs parameter temporary cleanup", async () => {
await simulator.fs.opfs.writeFile("parameters/rs274ngc.var.new", "stale temporary parameter file\n"); await simulator.fs.opfs.writeFile("parameters/rs274ngc.var.new", "stale temporary parameter file\n");
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters() writes // 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", "5161\t2.000000\n");
simulator.fs.module.FS.writeFile("rs274ngc.var.bak", "5161\t1.000000\n"); simulator.fs.module.FS.writeFile("rs274ngc.var.bak", "5161\t1.000000\n");
await simulator.fs.opfs.persistParameterFile("parameters/backup-bridge.var"); await simulator.fs.opfs.persistParameterFile("parameters/backup-bridge.var");
const opfsParameterBackup = new TextDecoder().decode( const opfsParameterBackup = await readOpfsText(simulator, "parameters/backup-bridge.var.bak");
await simulator.fs.opfs.readFile("parameters/backup-bridge.var.bak"),
);
expectIncludes(opfsParameterBackup, "5161\t1.000000", expectIncludes(opfsParameterBackup, "5161\t1.000000",
"LinuxCNC parameter backup file was not bridged into OPFS"); "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 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");
removeWasmPathIfExists(simulator, "/rs274ngc.var.bak"); removeWasmPathIfExists(simulator, "/rs274ngc.var.bak");
await simulator.parseWithParameterFile( await simulator.parseWithParameterFile(
"G21 G90\nM30\n", "G21 G90\nM30\n",
"parameters/rs274ngc.var", "parameters/stale-backup-cleanup.var",
"linuxcnc", "linuxcnc",
{ backend: "linuxcnc-rs274" }, { backend: "linuxcnc-rs274" },
); );
await expectMissingOpfsFile( 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", "stale LinuxCNC parameter backup file remained in OPFS",
); );
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters() // 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 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.bak", "stale backup without main file\n");
await simulator.fs.opfs.writeFile("parameters/missing-main.var.new", "stale temporary 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 // LinuxCNC source basis: rs274ngc_pre.cc save_parameters() unlinks
@@ -124,13 +165,15 @@ export async function runBrowserOpfsParameterSections(context, simulator) {
"linuxcnc", "linuxcnc",
{ backend: "linuxcnc-rs274" }, { backend: "linuxcnc-rs274" },
); );
const missingMainParameterFile = new TextDecoder().decode( const missingMainParameterFile = await readOpfsText(simulator, "parameters/missing-main.var");
await simulator.fs.opfs.readFile("parameters/missing-main.var"),
);
expectIncludes(missingMainParameterFile, "5220\t1.000000", expectIncludes(missingMainParameterFile, "5220\t1.000000",
"LinuxCNC missing parameter file was not created through OPFS"); "LinuxCNC missing parameter file was not created through OPFS");
expectIncludes(missingMainParameterFile, "5161\t0.000000", expectIncludes(missingMainParameterFile, "5161\t0.000000",
"LinuxCNC missing parameter file did not include required G28 parameter"); "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( const missingMainRestoreEvents = await simulator.parseWithParameterFile(
"G21 G90\nF100\nO10 if [#5220 EQ 1]\nG1 X6\nO10 endif\n", "G21 G90\nF100\nO10 if [#5220 EQ 1]\nG1 X6\nO10 endif\n",
"parameters/missing-main.var", "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), 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"); "LinuxCNC-created missing OPFS parameter file was not restored on the next parse");
await expectMissingOpfsFile( const missingMainParameterBackup = await readOpfsText(simulator, "parameters/missing-main.var.bak");
() => simulator.fs.opfs.readFile("parameters/missing-main.var.bak"), expectIncludes(missingMainParameterBackup, "5220\t1.000000",
"stale LinuxCNC missing-parameter backup remained in OPFS", "LinuxCNC-created OPFS parameter file was not backed up on the next successful save");
);
expectMissingWasmPath( expectMissingWasmPath(
() => simulator.fs.module.FS.readFile("rs274ngc.var.bak"), () => simulator.fs.module.FS.readFile("rs274ngc.var.bak"),
"stale LinuxCNC missing-parameter backup remained in WASM FS", "stale LinuxCNC missing-parameter backup remained in WASM FS",
@@ -169,9 +211,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) {
"Parameter file out of order", "Parameter file out of order",
"LinuxCNC accepted an out-of-order OPFS parameter file", "LinuxCNC accepted an out-of-order OPFS parameter file",
); );
const outOfOrderParameterFile = new TextDecoder().decode( const outOfOrderParameterFile = await readOpfsText(simulator, "parameters/out-of-order.var");
await simulator.fs.opfs.readFile("parameters/out-of-order.var"),
);
expectText(outOfOrderParameterFile, "5162\t1.000000\n5161\t1.000000\n", expectText(outOfOrderParameterFile, "5162\t1.000000\n5161\t1.000000\n",
"LinuxCNC persisted an out-of-order OPFS parameter file after restore failure"); "LinuxCNC persisted an out-of-order OPFS parameter file after restore failure");
expectMissingWasmPath( expectMissingWasmPath(
@@ -189,9 +229,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) {
"Parameter number out of range", "Parameter number out of range",
"LinuxCNC accepted an out-of-range OPFS parameter file", "LinuxCNC accepted an out-of-range OPFS parameter file",
); );
const outOfRangeParameterFile = new TextDecoder().decode( const outOfRangeParameterFile = await readOpfsText(simulator, "parameters/out-of-range.var");
await simulator.fs.opfs.readFile("parameters/out-of-range.var"),
);
expectText(outOfRangeParameterFile, "0\t1.000000\n", expectText(outOfRangeParameterFile, "0\t1.000000\n",
"LinuxCNC persisted an out-of-range OPFS parameter file after restore failure"); "LinuxCNC persisted an out-of-range OPFS parameter file after restore failure");
expectMissingWasmPath( expectMissingWasmPath(
@@ -209,9 +247,7 @@ export async function runBrowserOpfsParameterSections(context, simulator) {
"", "",
"LinuxCNC accepted a failing parse that should not persist parameters", "LinuxCNC accepted a failing parse that should not persist parameters",
); );
const failedParseParameterFile = new TextDecoder().decode( const failedParseParameterFile = await readOpfsText(simulator, "parameters/failed-parse.var");
await simulator.fs.opfs.readFile("parameters/failed-parse.var"),
);
if (failedParseParameterFile.includes("5161\t1.000000")) { if (failedParseParameterFile.includes("5161\t1.000000")) {
throw new Error("LinuxCNC persisted failed parse parameter updates into OPFS"); throw new Error("LinuxCNC persisted failed parse parameter updates into OPFS");
} }