Files
wasm-simulator/web/test-browser-wasm-smoke-opfs-mirror-sections.js
cnc 586c28656d 验证 OPFS rename 持久清理
提示词:快速推进,避免纯 source-map-only,不自动推送。

结论:基于 LinuxCNC rs274ngc_pre.cc save_parameters() 的 filename + .new rename 语义,补强浏览器 OPFS moveFile 在新 WASM 实例中持久删除源文件并恢复目标 mirror 的覆盖;./test-native.sh 和 ./test-linuxcnc-source-link.sh 通过。
2026-06-06 06:31:30 +08:00

173 lines
9.6 KiB
JavaScript

export async function runBrowserOpfsMirrorSections(context, simulator) {
const {
expectEvent,
expectIncludes,
expectOpfsDirectoryStat,
expectMissingWasmPath,
expectText,
expectWasmFilesMissing,
near,
readOpfsText,
runSection,
withSmokeSimulator,
} = context;
await runSection("opfs wasm mirror persist file", async () => {
const wasmPath = simulator.fs.opfs.resolvePath("programs/browser-smoke.ngc");
const wasmDecoded = new TextDecoder().decode(simulator.fs.module.FS.readFile(wasmPath));
expectText(wasmDecoded, "G21 G90\nG0 X1\n", "OPFS file was not mirrored into the WASM filesystem");
const persistedPath = simulator.fs.opfs.resolvePath("programs/browser-persisted.ngc");
simulator.fs.module.FS.writeFile(persistedPath, new TextEncoder().encode("G21 G90\nG0 X2\n"));
const persisted = new TextDecoder().decode(await simulator.fs.opfs.persistFile("programs/browser-persisted.ngc"));
expectText(persisted, "G21 G90\nG0 X2\n", "WASM filesystem file was not persisted into OPFS");
const persistedDecoded = new TextDecoder().decode(
await simulator.fs.opfs.readFile("programs/browser-persisted.ngc"),
);
expectText(persistedDecoded, "G21 G90\nG0 X2\n",
"persisted WASM filesystem file was not readable from OPFS");
});
await runSection("opfs persisted wasm mirror parse in new instance", async () => {
await withSmokeSimulator(async (restoredMirrorSimulator) => {
// LinuxCNC source basis: rs274ngc_pre.cc read_text() reads
// program blocks after the OPFS file is mirrored into the WASM FS.
const persistedProgram = new TextDecoder().decode(
await restoredMirrorSimulator.fs.opfs.readFile("programs/browser-persisted.ngc"),
);
expectText(persistedProgram, "G21 G90\nG0 X2\n",
"persisted WASM mirror file was not restored from OPFS in a new instance");
const persistedEvents = await restoredMirrorSimulator.parseFile(
"programs/browser-persisted.ngc",
"linuxcnc",
{ backend: "linuxcnc-rs274" },
);
expectEvent(persistedEvents, (event) => event.type === "rapid" && near(event.end.x, 2),
"persisted WASM mirror file was not parsed through LinuxCNC in a new instance");
});
});
await runSection("opfs copy and move mirror files", async () => {
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters()
// links the current parameter file to filename + ".bak", then
// writes filename + ".new" and renames it over the parameter file.
await simulator.fs.opfs.writeFile("parameters/move-main.var", "5161\t1.000000\n");
const copiedParameterBackup = new TextDecoder().decode(
await simulator.fs.opfs.copyFile("parameters/move-main.var", "parameters/move-main.var.bak"),
);
expectText(copiedParameterBackup, "5161\t1.000000\n",
"OPFS copyFile did not return the copied file contents");
const copiedParameterBackupFile = new TextDecoder().decode(
await simulator.fs.opfs.readFile("parameters/move-main.var.bak"),
);
expectText(copiedParameterBackupFile, "5161\t1.000000\n",
"OPFS copyFile did not create the backup file");
const copiedParameterBackupWasmPath = simulator.fs.opfs.resolvePath("parameters/move-main.var.bak");
const copiedParameterBackupWasm = new TextDecoder().decode(
simulator.fs.module.FS.readFile(copiedParameterBackupWasmPath),
);
expectText(copiedParameterBackupWasm, "5161\t1.000000\n",
"OPFS copyFile did not update the WASM mirror destination");
await simulator.fs.opfs.writeFile("parameters/move-main.var.new", "5161\t2.000000\n");
await simulator.fs.opfs.moveFile("parameters/move-main.var.new", "parameters/move-main.var");
const movedParameterFile = new TextDecoder().decode(
await simulator.fs.opfs.readFile("parameters/move-main.var"),
);
expectText(movedParameterFile, "5161\t2.000000\n",
"OPFS moveFile did not replace the destination file");
if (await simulator.fs.opfs.exists("parameters/move-main.var.new")) {
throw new Error("OPFS moveFile left the source file behind");
}
const movedParameterWasmPath = simulator.fs.opfs.resolvePath("parameters/move-main.var");
const movedParameterWasm = new TextDecoder().decode(
simulator.fs.module.FS.readFile(movedParameterWasmPath),
);
expectText(movedParameterWasm, "5161\t2.000000\n",
"OPFS moveFile did not update the WASM mirror destination");
const movedTemporaryWasmPath = simulator.fs.opfs.resolvePath("parameters/move-main.var.new");
expectMissingWasmPath(
() => simulator.fs.module.FS.stat(movedTemporaryWasmPath),
"OPFS moveFile left the WASM mirror source behind",
);
await withSmokeSimulator(async (restoredMoveSimulator) => {
const restoredMovedParameterFile = await readOpfsText(restoredMoveSimulator, "parameters/move-main.var");
expectText(restoredMovedParameterFile, "5161\t2.000000\n",
"OPFS moveFile destination was not persisted for a new WASM instance");
if (await restoredMoveSimulator.fs.opfs.exists("parameters/move-main.var.new")) {
throw new Error("OPFS moveFile source was still persisted for a new WASM instance");
}
const restoredMovedParameterWasmPath = restoredMoveSimulator.fs.opfs.resolvePath("parameters/move-main.var");
const restoredMovedParameterWasm = new TextDecoder().decode(
restoredMoveSimulator.fs.module.FS.readFile(restoredMovedParameterWasmPath),
);
expectText(restoredMovedParameterWasm, "5161\t2.000000\n",
"OPFS moveFile destination was not mirrored for a new WASM instance");
const restoredMovedTemporaryWasmPath = restoredMoveSimulator.fs.opfs.resolvePath("parameters/move-main.var.new");
expectMissingWasmPath(
() => restoredMoveSimulator.fs.module.FS.stat(restoredMovedTemporaryWasmPath),
"OPFS moveFile source was mirrored for a new WASM instance",
);
});
await simulator.fs.opfs.writeFile("parameters/mirror-conflict-source.var", "5161\t3.000000\n");
const copyConflictPath = simulator.fs.opfs.resolvePath("parameters/mirror-conflict-copy.var");
simulator.fs.module.FS.mkdir(copyConflictPath);
await simulator.fs.opfs.copyFile("parameters/mirror-conflict-source.var", "parameters/mirror-conflict-copy.var");
const copyConflictMode = simulator.fs.module.FS.stat(copyConflictPath).mode;
if (!simulator.fs.module.FS.isFile(copyConflictMode)) {
throw new Error("OPFS copyFile did not replace a WASM mirror directory with a file");
}
expectText(
new TextDecoder().decode(simulator.fs.module.FS.readFile(copyConflictPath)),
"5161\t3.000000\n",
"OPFS copyFile did not write copied contents over a WASM mirror directory",
);
await simulator.fs.opfs.writeFile("parameters/mirror-conflict-move.var.new", "5161\t4.000000\n");
const moveConflictPath = simulator.fs.opfs.resolvePath("parameters/mirror-conflict-move.var");
simulator.fs.module.FS.mkdir(moveConflictPath);
await simulator.fs.opfs.moveFile("parameters/mirror-conflict-move.var.new", "parameters/mirror-conflict-move.var");
const moveConflictMode = simulator.fs.module.FS.stat(moveConflictPath).mode;
if (!simulator.fs.module.FS.isFile(moveConflictMode)) {
throw new Error("OPFS moveFile did not replace a WASM mirror directory with a file");
}
expectText(
new TextDecoder().decode(simulator.fs.module.FS.readFile(moveConflictPath)),
"5161\t4.000000\n",
"OPFS moveFile did not write moved contents over a WASM mirror directory",
);
});
await runSection("opfs write truncates wasm mirror", async () => {
await simulator.fs.opfs.writeFile("programs/truncate.ngc", "G21 G90\nG0 X12345\nM30\n");
await simulator.fs.opfs.writeFile("programs/truncate.ngc", "G21\n");
const truncatedProgram = new TextDecoder().decode(await simulator.fs.opfs.readFile("programs/truncate.ngc"));
expectText(truncatedProgram, "G21\n", "OPFS writeFile did not truncate stale program bytes");
const truncatedWasmPath = simulator.fs.opfs.resolvePath("programs/truncate.ngc");
const truncatedWasmProgram = new TextDecoder().decode(simulator.fs.module.FS.readFile(truncatedWasmPath));
expectText(truncatedWasmProgram, "G21\n", "OPFS writeFile did not truncate the WASM mirror");
});
await runSection("opfs parameter backup bridge failure is non-fatal", async () => {
// LinuxCNC source basis: rs274ngc_pre.cc save_parameters()
// reports link(filename, filename + ".bak") failure with perror()
// but still renames filename + ".new" over the parameter file.
simulator.fs.module.FS.writeFile("rs274ngc.var", "5161\t12.000000\n");
simulator.fs.module.FS.writeFile("rs274ngc.var.bak", "5161\t11.000000\n");
const backupBridgeDirectory = simulator.fs.opfs.resolvePath("parameters/backup-bridge-directory.var.bak");
simulator.fs.module.FS.mkdir(backupBridgeDirectory);
await simulator.fs.opfs.persistDirectory("parameters/backup-bridge-directory.var.bak");
await simulator.fs.opfs.persistParameterFile("parameters/backup-bridge-directory.var");
expectIncludes(
await readOpfsText(simulator, "parameters/backup-bridge-directory.var"),
"5161\t12.000000",
"LinuxCNC parameter bridge did not persist the main file when backup link failed",
);
await expectOpfsDirectoryStat(
simulator,
"parameters/backup-bridge-directory.var.bak",
"LinuxCNC parameter backup bridge removed an existing OPFS backup directory",
);
expectWasmFilesMissing(simulator.fs.module.FS, ["rs274ngc.var", "rs274ngc.var.new", "rs274ngc.var.bak"], (parameterPath) => `LinuxCNC parameter scratch file remained outside OPFS after non-fatal backup bridge failure: ${parameterPath}`);
});
}