Add OPFS G-code staging and browser simulation checks
This commit is contained in:
@@ -34,6 +34,16 @@
|
||||
}
|
||||
return win.webRtcp5AxisSimulation.getState();
|
||||
}
|
||||
async function waitForMachineFileStaging(win) {
|
||||
for (let attempt = 0; attempt < 120; attempt += 1) {
|
||||
const state = win.webRtcp5AxisSimulation.getState();
|
||||
if (state.machineFileStaging?.status === "staged" && state.machineFileStaging?.save?.fileCount >= 5) {
|
||||
return state;
|
||||
}
|
||||
await wait(25);
|
||||
}
|
||||
return win.webRtcp5AxisSimulation.getState();
|
||||
}
|
||||
|
||||
async function runSmoke() {
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -44,6 +54,14 @@
|
||||
|
||||
const doc = frame.contentDocument;
|
||||
const win = frame.contentWindow;
|
||||
const runtimeErrors = [];
|
||||
win.addEventListener("error", (event) => {
|
||||
runtimeErrors.push(event.message || String(event.error || "window error"));
|
||||
});
|
||||
win.addEventListener("unhandledrejection", (event) => {
|
||||
runtimeErrors.push(event.reason?.message || String(event.reason || "unhandled rejection"));
|
||||
});
|
||||
win.__WEB_RTCP_FORCE_OPFS_UNAVAILABLE__ = true;
|
||||
const regions = [
|
||||
"titlebar",
|
||||
"preview",
|
||||
@@ -301,6 +319,20 @@
|
||||
if (!doc.querySelector('[data-program-switchkins-summary]')?.textContent.includes("0 events")) {
|
||||
throw new Error("program switchkins summary DOM did not render zero-event state");
|
||||
}
|
||||
if (!win.webRtcp5AxisSimulation.machineFileSeedReady) {
|
||||
throw new Error("missing automatic machine file seed promise");
|
||||
}
|
||||
await win.webRtcp5AxisSimulation.machineFileSeedReady;
|
||||
const autoStagedState = await waitForMachineFileStaging(win);
|
||||
if (autoStagedState.machineFileStaging?.save?.summary?.gcodeFileCount !== 16) {
|
||||
throw new Error(`automatic machine file seeding did not preload full project G-code set: ${JSON.stringify(autoStagedState.machineFileStaging?.save?.summary)}`);
|
||||
}
|
||||
if (autoStagedState.machineFileStaging?.gcodeSources?.length < 8) {
|
||||
throw new Error(`automatic machine file seeding did not expose LinuxCNC 5-axis demos: ${JSON.stringify(autoStagedState.machineFileStaging)}`);
|
||||
}
|
||||
if (!doc.querySelector('[data-machine-file-staging="status"]')?.textContent.includes("staged")) {
|
||||
throw new Error("automatic machine file staging status did not render");
|
||||
}
|
||||
win.webRtcp5AxisSimulation.dispatch({
|
||||
type: "LOAD_PROGRAM",
|
||||
filename: "operator-arc-demo.ngc",
|
||||
@@ -328,12 +360,27 @@
|
||||
if (stagedMachineFiles.save.status !== "saved" || stagedMachineFiles.save.fileCount < 5) {
|
||||
throw new Error(`machine file staging did not save files: ${JSON.stringify(stagedMachineFiles.save)}`);
|
||||
}
|
||||
if (stagedMachineFiles.save.summary?.gcodeFileCount !== 16) {
|
||||
throw new Error(`manual machine file staging did not preserve full project G-code set: ${JSON.stringify(stagedMachineFiles.save.summary)}`);
|
||||
}
|
||||
if (
|
||||
stagedMachineFiles.save.storageMode !== "memory-fallback" ||
|
||||
stagedMachineFiles.save.storageCapability?.opfsUnavailable !== true
|
||||
) {
|
||||
throw new Error(`machine file staging did not use OPFS fallback: ${JSON.stringify(stagedMachineFiles.save.storageCapability)}`);
|
||||
}
|
||||
if (!stagedMachineFiles.save.files.some((file) => file.sourceRel.endsWith("remap_subs/428remap.ngc"))) {
|
||||
throw new Error("machine file staging did not include M428 remap");
|
||||
}
|
||||
if (!doc.querySelector('[data-machine-file-staging="status"]')?.textContent.includes("staged")) {
|
||||
throw new Error("machine file staging status did not render");
|
||||
}
|
||||
if (
|
||||
!doc.querySelector('[data-machine-file-staging="status"]')?.textContent.includes("memory-fallback") ||
|
||||
!doc.querySelector('[data-machine-file-staging="status"]')?.textContent.includes("forced_unavailable")
|
||||
) {
|
||||
throw new Error("machine file staging status did not render OPFS fallback reason");
|
||||
}
|
||||
const sourceSelect = doc.querySelector('[data-action="select-linuxcnc-gcode-source"]');
|
||||
if (!sourceSelect || sourceSelect.options.length < 4) {
|
||||
throw new Error("LinuxCNC 5-axis G-code source selector did not render staged demos");
|
||||
@@ -355,6 +402,45 @@
|
||||
if (!doc.querySelector('[data-program-source]')?.textContent.includes("linuxcnc-vendored-5axis-gcode")) {
|
||||
throw new Error("program source DOM did not show LinuxCNC vendored source");
|
||||
}
|
||||
if (linuxCncSourceState.programExecution?.summary?.motionEventCount < 1000) {
|
||||
throw new Error(`LinuxCNC source program did not produce canonical motion: ${JSON.stringify(linuxCncSourceState.programExecution?.summary)}`);
|
||||
}
|
||||
if (linuxCncSourceState.programExecution?.summary?.plannerRuntimeReady !== true) {
|
||||
throw new Error(`LinuxCNC source program did not produce TP timing: ${JSON.stringify(linuxCncSourceState.programExecution?.plannerTiming)}`);
|
||||
}
|
||||
if (linuxCncSourceState.programExecutionTiming?.samples?.length < 1) {
|
||||
throw new Error("LinuxCNC source program did not expose TP samples");
|
||||
}
|
||||
if (linuxCncSourceState.programExecution?.summary?.switchkinsEventCount < 2) {
|
||||
throw new Error("LinuxCNC source program did not preserve switchkins events");
|
||||
}
|
||||
if (linuxCncSourceState.rtcpState !== "on" || linuxCncSourceState.kinsType !== "tcp-xyzac") {
|
||||
throw new Error(`LinuxCNC source program did not apply initial RTCP switchkins state: ${linuxCncSourceState.rtcpState}/${linuxCncSourceState.kinsType}`);
|
||||
}
|
||||
canvas = doc.querySelector("[data-five-axis-canvas]");
|
||||
if (
|
||||
canvas.dataset.threeProgramPreviewSource !== "linuxcnc-interpreter-wasm" ||
|
||||
Number(canvas.dataset.threePathPoints ?? 0) < 1 ||
|
||||
Number(canvas.dataset.threeExecutedPathPoints ?? 0) < 1 ||
|
||||
canvas.dataset.threeToolExecutionMarker !== "true"
|
||||
) {
|
||||
throw new Error(`Three.js preview did not expose LinuxCNC source program path and execution trace: ${JSON.stringify(canvas.dataset)}`);
|
||||
}
|
||||
if (
|
||||
canvas.dataset.threeSceneMode !== "program-preview-and-tool-execution" ||
|
||||
canvas.dataset.threeToolpathPreviewSource !== "linuxcnc_interpreter_canonical_motion" ||
|
||||
canvas.dataset.threeToolExecutionTraceSource !== "linuxcnc_tp_samples_or_task_motion_hal_feedback" ||
|
||||
canvas.dataset.threePathFitBounds !== "ok" ||
|
||||
canvas.dataset.threeCurrentSegmentHighlight !== "ok" ||
|
||||
canvas.dataset.threeRapidFeedVisualDistinction !== "ok" ||
|
||||
canvas.dataset.threeNoGcodeSemanticsGeneration !== "ok"
|
||||
) {
|
||||
throw new Error(`Three.js M20 source/runtime display gate failed: ${JSON.stringify(canvas.dataset)}`);
|
||||
}
|
||||
if (Number(canvas.dataset.threeRapidPathPoints ?? 0) < 1 || Number(canvas.dataset.threeFeedPathPoints ?? 0) < 1) {
|
||||
throw new Error(`Three.js rapid/feed distinction did not expose path points: ${JSON.stringify(canvas.dataset)}`);
|
||||
}
|
||||
assertCanvasNonblank(canvas, "desktop LinuxCNC source Three.js preview");
|
||||
win.webRtcp5AxisSimulation.dispatch({ type: "RUN_MACHINE_FILE_PROGRAM" });
|
||||
const machineFileRunState = await waitForMachineFileExecution(win);
|
||||
if (machineFileRunState.machineFileExecution?.summary?.machineFileExecutionReady !== true) {
|
||||
@@ -383,12 +469,18 @@
|
||||
if (!["opfs", "memory-fallback"].includes(savedSession.storageMode)) {
|
||||
throw new Error(`saveSession used unexpected storage mode: ${savedSession.storageMode}`);
|
||||
}
|
||||
if (savedSession.storageMode !== "memory-fallback" || savedSession.storageCapability?.opfsUnavailable !== true) {
|
||||
throw new Error(`saveSession did not use OPFS fallback: ${JSON.stringify(savedSession.storageCapability)}`);
|
||||
}
|
||||
if (!doc.querySelector('[data-session-persistence="status"]')?.textContent.includes("saved")) {
|
||||
throw new Error("session save status did not render");
|
||||
}
|
||||
if (!doc.querySelector('[data-session-persistence="status"]')?.textContent.includes(savedSession.storageMode)) {
|
||||
throw new Error("session save status did not render storage mode");
|
||||
}
|
||||
if (!doc.querySelector('[data-session-persistence="status"]')?.textContent.includes("opfs unavailable")) {
|
||||
throw new Error("session save status did not render OPFS unavailable fallback");
|
||||
}
|
||||
if (doc.querySelector('[data-active-program-line]')?.textContent !== "Current line 2") {
|
||||
throw new Error("loaded program did not render first LinuxCNC motion line");
|
||||
}
|
||||
@@ -413,10 +505,22 @@
|
||||
canvas = doc.querySelector("[data-five-axis-canvas]");
|
||||
if (
|
||||
Number(canvas.dataset.threeExecutedPathPoints ?? 0) < 1 ||
|
||||
canvas.dataset.threeToolExecutionMarker !== "true"
|
||||
canvas.dataset.threeToolExecutionMarker !== "true" ||
|
||||
canvas.dataset.threeCurrentSegmentHighlight !== "ok"
|
||||
) {
|
||||
throw new Error(`Three.js preview did not display tool execution progress: ${JSON.stringify(canvas.dataset)}`);
|
||||
}
|
||||
const originalFrameStyle = frame.getAttribute("style") || "";
|
||||
frame.style.width = "390px";
|
||||
frame.style.height = "760px";
|
||||
await wait(100);
|
||||
canvas = doc.querySelector("[data-five-axis-canvas]");
|
||||
if (canvas.dataset.threeReady !== "true" || canvas.dataset.threePathFitBounds !== "ok") {
|
||||
throw new Error(`mobile Three.js preview did not retain fit bounds: ${JSON.stringify(canvas.dataset)}`);
|
||||
}
|
||||
assertCanvasNonblank(canvas, "mobile LinuxCNC source Three.js preview");
|
||||
frame.setAttribute("style", originalFrameStyle);
|
||||
await wait(50);
|
||||
const taskHalRunState = win.webRtcp5AxisSimulation.getState();
|
||||
if (taskHalRunState.fullExecutionBoundary?.semanticBoundary !== "linuxcnc_task_motion_hal_wasm_simulation_runtime") {
|
||||
throw new Error(`full execution boundary did not promote task/HAL simulation runtime: ${JSON.stringify(taskHalRunState.fullExecutionBoundary)}`);
|
||||
@@ -427,6 +531,27 @@
|
||||
if (!doc.querySelector('[data-full-execution-boundary="status"]')?.textContent.includes("full ready")) {
|
||||
throw new Error("full execution boundary status did not render full ready state");
|
||||
}
|
||||
const hostNativeBoundary = doc.querySelector('[data-full-execution-boundary="host-native"]')?.textContent || "";
|
||||
for (const requiredHostNativeState of [
|
||||
"hardware drive false",
|
||||
"host realtime false",
|
||||
"external user-M false",
|
||||
"tool DB false",
|
||||
]) {
|
||||
if (!hostNativeBoundary.includes(requiredHostNativeState)) {
|
||||
throw new Error(`host/native boundary diagnostic did not render ${requiredHostNativeState}: ${hostNativeBoundary}`);
|
||||
}
|
||||
}
|
||||
for (const [field, expected] of Object.entries({
|
||||
hardwareDrive: false,
|
||||
hostRealtimeKernel: false,
|
||||
externalUserMProcessReady: false,
|
||||
toolDbProcessReady: false,
|
||||
})) {
|
||||
if (taskHalRunState.fullExecutionBoundary?.[field] !== expected) {
|
||||
throw new Error(`full execution boundary ${field} drifted: ${JSON.stringify(taskHalRunState.fullExecutionBoundary)}`);
|
||||
}
|
||||
}
|
||||
if (!doc.querySelector('[data-task-hal-runtime="readiness"]')?.textContent.includes("sync")) {
|
||||
throw new Error("task/HAL readiness diagnostic did not render sync");
|
||||
}
|
||||
@@ -545,6 +670,9 @@
|
||||
if (uiRestoredSession.status !== "restored" || uiRestoredSession.storageMode !== uiSavedSession.storageMode) {
|
||||
throw new Error(`UI Restore Session did not restore from default storage fallback: ${JSON.stringify(uiRestoredSession)}`);
|
||||
}
|
||||
if (uiRestoredSession.storageMode !== "memory-fallback") {
|
||||
throw new Error(`UI Restore Session did not preserve memory fallback mode: ${JSON.stringify(uiRestoredSession)}`);
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().machine.allHomed !== true) {
|
||||
doc.querySelector('[data-action="mode-manual"]').click();
|
||||
await wait(50);
|
||||
@@ -673,6 +801,9 @@
|
||||
if (win.webRtcp5AxisSimulation.getState().runState !== "estopped") {
|
||||
throw new Error("E-STOP did not update run state");
|
||||
}
|
||||
if (runtimeErrors.length > 0) {
|
||||
throw new Error(`public HTTP fallback smoke saw uncaught runtime errors: ${runtimeErrors.join(" | ")}`);
|
||||
}
|
||||
|
||||
result.textContent = "gmoccapy_shell_smoke=ok";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user