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}`); }); }