export async function runBrowserOpfsBasicSections(context, simulator) { const { expectErrorContaining, expectEvent, expectMissingWasmPath, expectText, near, runSection, withSmokeSimulator, } = context; await runSection("opfs workspace clear and healthcheck", async () => { if (!simulator.fs.opfs) { throw new Error("expected OPFS workspace to be available in browser smoke"); } await simulator.fs.opfs.clear(); if (simulator.fs.opfs.workspacePath !== "cnc-simulator-smoke") { throw new Error("unexpected OPFS workspace path"); } await simulator.fs.opfs.writeFile("healthcheck.ngc", "G21 G90\n"); if (!(await simulator.fs.opfs.exists("healthcheck.ngc"))) { throw new Error("OPFS exists did not find a persisted file"); } if (await simulator.fs.opfs.exists("missing-healthcheck.ngc")) { throw new Error("OPFS exists reported a missing file"); } const healthcheckStat = await simulator.fs.opfs.stat("healthcheck.ngc"); if (healthcheckStat.kind !== "file" || healthcheckStat.size !== 8) { throw new Error("OPFS stat did not report the persisted healthcheck file size"); } const clearCheck = new TextDecoder().decode(await simulator.fs.opfs.readFile("healthcheck.ngc")); expectText(clearCheck, "G21 G90\n", "OPFS workspace did not survive clear/write round trip"); }); await runSection("opfs default mount check", async () => { await withSmokeSimulator((defaultMountSimulator) => { if (!defaultMountSimulator.fs.opfs || defaultMountSimulator.fs.opfs.mountPoint !== "/cnc") { throw new Error("default OPFS mount point was not preserved"); } }); }); await runSection("opfs program write and read", async () => { const encoded = new TextEncoder().encode("G21 G90\nG0 X1\n"); await simulator.fs.opfs.writeFile("programs/browser-smoke.ngc", encoded); const decoded = new TextDecoder().decode( await simulator.fs.opfs.readFile("programs/browser-smoke.ngc"), ); expectText(decoded, "G21 G90\nG0 X1\n", "OPFS round trip did not preserve browser smoke program"); }); await runSection("opfs unsafe path rejection", async () => { for (const unsafePath of ["../escape.ngc", "programs/../escape.ngc", "/escape.ngc", "programs//escape.ngc"]) { await expectErrorContaining( () => simulator.fs.opfs.writeFile(unsafePath, "G21\n"), "inside the CNC workspace", `OPFS accepted unsafe path ${unsafePath}`, ); } for (const [operation, action] of [ ["readFile", () => simulator.fs.opfs.readFile("../escape.ngc")], ["readDirectory", () => simulator.fs.opfs.readDirectory("../escape")], ["persistFile", () => simulator.fs.opfs.persistFile("../escape.ngc")], ["persistDirectory", () => simulator.fs.opfs.persistDirectory("../escape")], ["copyFile", () => simulator.fs.opfs.copyFile("../escape.ngc", "programs/escape.ngc")], ["copyFileTarget", () => simulator.fs.opfs.copyFile("healthcheck.ngc", "../escape.ngc")], ["exists", () => simulator.fs.opfs.exists("../escape.ngc")], ["stat", () => simulator.fs.opfs.stat("../escape.ngc")], ["moveFile", () => simulator.fs.opfs.moveFile("../escape.ngc", "programs/escape.ngc")], ["moveFileTarget", () => simulator.fs.opfs.moveFile("healthcheck.ngc", "../escape.ngc")], ["removeFile", () => simulator.fs.opfs.removeFile("../escape.ngc")], ["removeDirectory", () => simulator.fs.opfs.removeDirectory("../escape")], ["parseFile", () => simulator.parseFile("../escape.ngc", "linuxcnc", { backend: "linuxcnc-rs274" })], [ "parseWithParameterFile", () => simulator.parseWithParameterFile("G21 G90\nM30\n", "../escape.var", "linuxcnc", { backend: "linuxcnc-rs274", }), ], [ "parseFileWithParameterFileProgram", () => simulator.parseFileWithParameterFile("../escape.ngc", "parameters/escape.var", "linuxcnc", { backend: "linuxcnc-rs274", }), ], [ "parseFileWithParameterFileParameter", () => simulator.parseFileWithParameterFile("programs/browser-smoke.ngc", "../escape.var", "linuxcnc", { backend: "linuxcnc-rs274", }), ], ]) { await expectErrorContaining( action, "inside the CNC workspace", `OPFS ${operation} accepted an unsafe path`, ); } }); await runSection("opfs program parse and restore", async () => { const opfsProgramEvents = await simulator.parseFile("programs/browser-smoke.ngc", "linuxcnc", { backend: "linuxcnc-rs274", }); expectEvent(opfsProgramEvents, (event) => event.type === "rapid" && near(event.end.x, 1), "OPFS program file was not parsed through LinuxCNC WASM"); for (const parameterPath of ["rs274ngc.var", "rs274ngc.var.new", "rs274ngc.var.bak"]) { expectMissingWasmPath( () => simulator.fs.module.FS.stat(parameterPath), `browser parseFile left LinuxCNC parameter state outside OPFS: ${parameterPath}`, ); } await withSmokeSimulator(async (restoredProgramSimulator) => { // LinuxCNC source basis: rs274ngc_pre.cc read_text() reads // blocks from the program stream after the OPFS mirror is loaded. const restoredProgramEvents = await restoredProgramSimulator.parseFile( "programs/browser-smoke.ngc", "linuxcnc", { backend: "linuxcnc-rs274" }, ); expectEvent(restoredProgramEvents, (event) => event.type === "rapid" && near(event.end.x, 1), "LinuxCNC OPFS program file was not restored across WASM simulator instances"); }); }); }