新增 OPFS session 独立 browser workflow gate
This commit is contained in:
@@ -138,7 +138,7 @@
|
||||
"workflow overview release artifact validation API name",
|
||||
);
|
||||
assertEqual(releaseArtifactValidation.ready, true, "workflow overview release artifact validation readiness");
|
||||
assertEqual(releaseArtifactValidation.gateCount, 10, "workflow overview release artifact validation gates");
|
||||
assertEqual(releaseArtifactValidation.gateCount, 11, "workflow overview release artifact validation gates");
|
||||
assertEqual(
|
||||
releaseArtifactValidationSummary.statusLine,
|
||||
"Ready: Artifact evidence complete",
|
||||
|
||||
203
wasm-port/tests/browser/opfs_session_workflow_smoke.html
Normal file
203
wasm-port/tests/browser/opfs_session_workflow_smoke.html
Normal file
@@ -0,0 +1,203 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>LinuxCNC OPFS Session Workflow Smoke</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="status">running</pre>
|
||||
<script type="module">
|
||||
import {
|
||||
createLinuxCncIniSdk,
|
||||
createLinuxCncInterpSdk,
|
||||
createMachineSessionPersistenceSummary,
|
||||
gcodeProgramPath,
|
||||
loadGcodeProgram,
|
||||
loadMachineSessionFromOpfs,
|
||||
loadMachineSessionSnapshot,
|
||||
loadMachineTextFiles,
|
||||
machineFilePaths,
|
||||
readMachineSessionReadiness,
|
||||
saveGcodeProgram,
|
||||
saveMachineSessionSnapshot,
|
||||
saveMachineTextFiles,
|
||||
} from "../../runtime/sdk/src/index.js";
|
||||
|
||||
const status = document.getElementById("status");
|
||||
|
||||
function assertEqual(actual, expected, label) {
|
||||
if (actual !== expected) {
|
||||
throw new Error(`${label}: expected ${expected}, got ${actual}`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertIncludes(values, expected, label) {
|
||||
if (!values.includes(expected)) {
|
||||
throw new Error(`${label}: expected ${expected} in ${values.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const machineId = "browser-opfs-session-gate";
|
||||
const sessionId = "browser-opfs-session-gate-session";
|
||||
const snapshotFilename = "browser-opfs-session-gate.json";
|
||||
const gcodeFilename = "browser-opfs-session-gate.ngc";
|
||||
const gcodeOpfsPath = gcodeProgramPath(gcodeFilename);
|
||||
const paths = machineFilePaths(machineId);
|
||||
const iniText = `[EMC]
|
||||
MACHINE = browser-opfs-session-gate
|
||||
|
||||
[TRAJ]
|
||||
LINEAR_UNITS = mm
|
||||
COORDINATES = X Y Z
|
||||
|
||||
[KINS]
|
||||
KINEMATICS = trivkins
|
||||
JOINTS = 3
|
||||
|
||||
[RS274NGC]
|
||||
PARAMETER_FILE = linuxcnc.var
|
||||
|
||||
[EMCIO]
|
||||
TOOL_TABLE = tool.tbl
|
||||
`;
|
||||
const parameterText = [
|
||||
"5161 0.0",
|
||||
"5162 0.0",
|
||||
"5220 1",
|
||||
"5221 0.0",
|
||||
"5399 0.0",
|
||||
"",
|
||||
].join("\n");
|
||||
const toolTableText = "T1 P1 Z0.0 D0.125 ;browser opfs session gate\n";
|
||||
const gcodeText = "G0 X0 Y0\nG1 X1 Y1 F20\nM2\n";
|
||||
|
||||
const initialReadiness = await readMachineSessionReadiness(machineId, {
|
||||
gcodeFilename,
|
||||
sessionId,
|
||||
snapshotFilename,
|
||||
});
|
||||
assertEqual(initialReadiness.ready, false, "initial readiness");
|
||||
assertIncludes(initialReadiness.missing, "ini", "initial missing INI");
|
||||
assertIncludes(initialReadiness.missing, "parameters", "initial missing parameters");
|
||||
assertIncludes(initialReadiness.missing, "toolTable", "initial missing tool table");
|
||||
assertIncludes(initialReadiness.missing, "gcode", "initial missing G-code");
|
||||
assertIncludes(initialReadiness.missing, "snapshot", "initial missing snapshot");
|
||||
|
||||
await saveMachineTextFiles(machineId, {
|
||||
ini: iniText,
|
||||
parameters: parameterText,
|
||||
toolTable: toolTableText,
|
||||
});
|
||||
await saveGcodeProgram(gcodeFilename, gcodeText);
|
||||
const snapshot = await saveMachineSessionSnapshot(sessionId, machineId, {
|
||||
filename: snapshotFilename,
|
||||
gcodeFilename,
|
||||
createdAt: "2026-06-16T00:00:00.000Z",
|
||||
metadata: {
|
||||
workflow: "opfs-session-workflow-browser-gate",
|
||||
defaultGcodeOpfsPath: gcodeOpfsPath,
|
||||
snapshotLabel: `${sessionId}/${snapshotFilename}`,
|
||||
},
|
||||
});
|
||||
|
||||
const files = await loadMachineTextFiles(machineId);
|
||||
assertEqual(files.ini, iniText, "loaded INI text");
|
||||
assertEqual(files.parameters, parameterText, "loaded parameter text");
|
||||
assertEqual(files.toolTable, toolTableText, "loaded tool table text");
|
||||
assertEqual(await loadGcodeProgram(gcodeFilename), gcodeText, "loaded G-code text");
|
||||
assertEqual(snapshot.payload.files.ini, paths.ini, "snapshot INI path");
|
||||
assertEqual(snapshot.payload.files.parameters, paths.parameters, "snapshot parameter path");
|
||||
assertEqual(snapshot.payload.files.toolTable, paths.toolTable, "snapshot tool table path");
|
||||
assertEqual(snapshot.payload.files.gcode, gcodeOpfsPath, "snapshot G-code path");
|
||||
assertEqual(
|
||||
(await loadMachineSessionSnapshot(sessionId, machineId, { filename: snapshotFilename }))
|
||||
.metadata.workflow,
|
||||
"opfs-session-workflow-browser-gate",
|
||||
"loaded snapshot workflow metadata",
|
||||
);
|
||||
|
||||
const readyReadiness = await readMachineSessionReadiness(machineId, {
|
||||
gcodeFilename,
|
||||
sessionId,
|
||||
snapshotFilename,
|
||||
});
|
||||
assertEqual(readyReadiness.ready, true, "ready readiness");
|
||||
assertEqual(readyReadiness.phase, "ready", "ready phase");
|
||||
assertEqual(readyReadiness.missing.length, 0, "ready missing count");
|
||||
assertEqual(readyReadiness.checks.length, 4, "ready file check count");
|
||||
assertEqual(readyReadiness.snapshotCheck.ok, true, "ready snapshot check");
|
||||
|
||||
const iniSdk = await createLinuxCncIniSdk();
|
||||
const interpSdk = await createLinuxCncInterpSdk();
|
||||
const loadedSession = await loadMachineSessionFromOpfs(interpSdk, machineId, {
|
||||
iniSdk,
|
||||
iniWasmPath: "/work/opfs-session-workflow.ini",
|
||||
parameterWasmPath: "/work/opfs-session-workflow.var",
|
||||
toolTableWasmPath: "/work/opfs-session-workflow.tbl",
|
||||
});
|
||||
assertEqual(loadedSession.machineId, machineId, "loaded session machine");
|
||||
assertEqual(loadedSession.ini.opfsPath, paths.ini, "loaded session INI OPFS path");
|
||||
assertEqual(loadedSession.ini.wasmPath, "/work/opfs-session-workflow.ini", "loaded session INI WASM path");
|
||||
assertEqual(loadedSession.parameters.opfsPath, paths.parameters, "loaded session parameter OPFS path");
|
||||
assertEqual(loadedSession.parameters.wasmPath, "/work/opfs-session-workflow.var", "loaded session parameter WASM path");
|
||||
assertEqual(loadedSession.toolTable.opfsPath, paths.toolTable, "loaded session tool table OPFS path");
|
||||
assertEqual(loadedSession.toolTable.wasmPath, "/work/opfs-session-workflow.tbl", "loaded session tool table WASM path");
|
||||
|
||||
const persistenceSummary = createMachineSessionPersistenceSummary({
|
||||
state: {
|
||||
badges: { opfs: "OPFS: ready" },
|
||||
machineFiles: {
|
||||
iniOpfsPath: paths.ini,
|
||||
parameterOpfsPath: paths.parameters,
|
||||
toolTableOpfsPath: paths.toolTable,
|
||||
gcodeOpfsPath,
|
||||
},
|
||||
sessionSnapshot: {
|
||||
snapshotLabel: `${sessionId}/${snapshotFilename}`,
|
||||
iniOpfsPath: paths.ini,
|
||||
parameterOpfsPath: paths.parameters,
|
||||
toolTableOpfsPath: paths.toolTable,
|
||||
gcodeOpfsPath,
|
||||
workflow: "opfs-session-workflow-browser-gate",
|
||||
},
|
||||
sessionReadiness: readyReadiness,
|
||||
sessionLoad: {
|
||||
iniWasmPath: loadedSession.ini.wasmPath,
|
||||
parameterWasmPath: loadedSession.parameters.wasmPath,
|
||||
toolTableWasmPath: loadedSession.toolTable.wasmPath,
|
||||
},
|
||||
},
|
||||
readonlyStatus: { ready: true },
|
||||
handoffWorkflowSummary: { ready: true },
|
||||
});
|
||||
assertEqual(persistenceSummary.apiName, "machine-session-persistence-summary", "summary API");
|
||||
assertEqual(persistenceSummary.ready, true, "summary ready");
|
||||
assertEqual(persistenceSummary.counts.machineFiles, 4, "summary machine file count");
|
||||
assertEqual(persistenceSummary.missing.length, 0, "summary missing count");
|
||||
assertEqual(
|
||||
persistenceSummary.rows.find(({ id }) => id === "session-readiness")?.value,
|
||||
"ready",
|
||||
"summary readiness row",
|
||||
);
|
||||
assertEqual(
|
||||
persistenceSummary.rows.find(({ id }) => id === "session-load")?.value,
|
||||
"loaded: /work/opfs-session-workflow.ini",
|
||||
"summary load row",
|
||||
);
|
||||
assertEqual(
|
||||
persistenceSummary.rows.find(({ id }) => id === "handoff-readiness")?.value,
|
||||
"ready",
|
||||
"summary handoff row",
|
||||
);
|
||||
|
||||
status.textContent = "browser_opfs_session_workflow_smoke=ok";
|
||||
console.log("browser_opfs_session_workflow_smoke=ok");
|
||||
} catch (error) {
|
||||
status.textContent = `browser_opfs_session_workflow_smoke=failed: ${error.message}`;
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -68,7 +68,7 @@
|
||||
);
|
||||
assertEqual(readyWorkflow.ready, true, "ready workflow readiness");
|
||||
assertEqual(readyWorkflow.httpStatus, 200, "ready workflow HTTP status");
|
||||
assertEqual(readyWorkflow.validation.gateCount, 10, "ready workflow gate count");
|
||||
assertEqual(readyWorkflow.validation.gateCount, 11, "ready workflow gate count");
|
||||
assertEqual(
|
||||
readyWorkflow.validationSummaryViewModel.statusLine,
|
||||
"Ready: Artifact evidence complete",
|
||||
|
||||
81
wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh
Executable file
81
wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
|
||||
|
||||
if [[ -z "$CHROMIUM" ]]; then
|
||||
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_INI_BUILD:-0}" != "1" ]]; then
|
||||
"$ROOT_DIR/tools/build_ini_panel.sh"
|
||||
fi
|
||||
if [[ "${SKIP_INTERP_BUILD:-0}" != "1" ]]; then
|
||||
"$ROOT_DIR/tools/build_wasm_core.sh"
|
||||
fi
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
PORT_FILE="$TMP_DIR/port"
|
||||
SERVER_LOG="$TMP_DIR/server.log"
|
||||
CHROME_PROFILE="$TMP_DIR/chrome-profile"
|
||||
mkdir -p "$CHROME_PROFILE"
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${SERVER_PID:-}" ]]; then
|
||||
kill "$SERVER_PID" 2>/dev/null || true
|
||||
wait "$SERVER_PID" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
|
||||
import functools
|
||||
import http.server
|
||||
import pathlib
|
||||
import socketserver
|
||||
import sys
|
||||
|
||||
root = pathlib.Path(sys.argv[1])
|
||||
port_file = pathlib.Path(sys.argv[2])
|
||||
|
||||
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
|
||||
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
|
||||
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
|
||||
httpd.serve_forever()
|
||||
PY
|
||||
SERVER_PID=$!
|
||||
|
||||
for _ in $(seq 1 100); do
|
||||
[[ -s "$PORT_FILE" ]] && break
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
if [[ ! -s "$PORT_FILE" ]]; then
|
||||
echo "OPFS/session workflow browser smoke HTTP server did not start" >&2
|
||||
cat "$SERVER_LOG" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PORT="$(cat "$PORT_FILE")"
|
||||
URL="http://127.0.0.1:$PORT/tests/browser/opfs_session_workflow_smoke.html"
|
||||
OUT="$TMP_DIR/chromium.out"
|
||||
|
||||
"$CHROMIUM" \
|
||||
--headless=new \
|
||||
--disable-gpu \
|
||||
--no-sandbox \
|
||||
--user-data-dir="$CHROME_PROFILE" \
|
||||
--virtual-time-budget=10000 \
|
||||
--dump-dom \
|
||||
"$URL" >"$OUT" 2>&1
|
||||
|
||||
if ! grep -Fq "browser_opfs_session_workflow_smoke=ok" "$OUT"; then
|
||||
echo "OPFS/session workflow browser smoke failed" >&2
|
||||
sed -n '1,220p' "$OUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "browser_opfs_session_workflow_smoke=ok"
|
||||
Reference in New Issue
Block a user