按规划继续工作

结论:解释器 WASM、SDK 统一入口、浏览器解释器 smoke 与兼容性文档已闭环,native 和 host/WASM/browser 验证全部通过。
This commit is contained in:
2026-06-08 07:24:06 +08:00
parent c28b629ff6
commit 706fd1e775
20 changed files with 629 additions and 122 deletions

View File

@@ -7,7 +7,7 @@
<body>
<pre id="status">running</pre>
<script type="module">
import { createLinuxCncIniSdk } from "../../runtime/sdk/src/linuxcnc-ini.js";
import { createLinuxCncIniSdk } from "../../runtime/sdk/src/index.js";
import { loadTextFile, saveTextFile } from "../../runtime/opfs/file-service.js";
import { machineIniPath } from "../../runtime/opfs/path-model.js";
import {

View File

@@ -0,0 +1,138 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC Interpreter Browser Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
import { createLinuxCncInterpSdk } from "../../runtime/sdk/src/index.js";
const status = document.getElementById("status");
async function fetchText(path) {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`${path}: HTTP ${response.status}`);
}
return response.text();
}
function verifyExpectedOutput(fixtureName, output, expectedText) {
const expectedLines = expectedText.split("\n").filter(Boolean);
for (const expectedLine of expectedLines) {
if (expectedLine.startsWith("absent=")) {
const forbidden = expectedLine.slice("absent=".length);
if (output.includes(forbidden)) {
throw new Error(`${fixtureName}: unexpected ${forbidden}`);
}
continue;
}
if (!output.includes(expectedLine)) {
throw new Error(`${fixtureName}: missing ${expectedLine}`);
}
}
}
try {
const interp = await createLinuxCncInterpSdk({
locateFile(path) {
if (path === "linuxcnc_interp.wasm") {
return "../../build/wasm/core/linuxcnc_interp.wasm";
}
return path;
},
print() {},
printErr(message) {
console.error(message);
},
});
for (const fixtureName of [
"minimal_linear",
"arc_semantics",
"length_units",
"modal_incremental",
"plane_selection",
"coordinate_offsets",
"g53_machine_coordinates",
"feed_control_modes",
"canned_cycles",
"numbered_params",
"comment_logging",
"tool_semantics",
"tool_table_setup",
"probe_semantics",
"spindle_orient",
"cutter_comp_motion",
"threading_sync",
"nurbs_g5_semantics",
"nurbs_g6_semantics",
"state_tag_motion",
"canon_runtime_edges",
"tool_reload",
"program_end_modal_reset",
]) {
const programText = await fetchText(`../fixtures/gcode/${fixtureName}.ngc`);
const expectedText = await fetchText(`../fixtures/canon/${fixtureName}.events`);
verifyExpectedOutput(fixtureName, interp.runProgram(programText), expectedText.trimEnd());
}
for (const errorFixtureName of [
"g1_zero_feed",
"arc_radius_mismatch",
"arc_zero_radius",
"g53_incremental",
"cutter_comp_plane_change",
"namedparam_readonly",
"numbered_param_readonly",
"tool_length_offset_not_found",
"tool_not_found",
]) {
verifyExpectedOutput(
errorFixtureName,
interp.runProgram(await fetchText(`../fixtures/gcode_errors/${errorFixtureName}.ngc`)),
(await fetchText(`../fixtures/canon_errors/${errorFixtureName}.expected`)).trimEnd(),
);
}
const positionParamsText = await fetchText("../fixtures/gcode/position_params.ngc");
const positionParamsPath = "/work/position_params.ngc";
interp.writeTextFile(positionParamsPath, positionParamsText);
verifyExpectedOutput(
"position_params_file",
interp.runFile(positionParamsPath),
(await fetchText("../fixtures/canon_file/position_params.events")).trimEnd(),
);
for (const fileFixtureName of ["file_open_reset", "percent_file_finish", "oword_subroutine"]) {
const programPath = `/work/${fileFixtureName}.ngc`;
interp.writeTextFile(programPath, await fetchText(`../fixtures/gcode/${fileFixtureName}.ngc`));
verifyExpectedOutput(
`${fileFixtureName}_file`,
interp.runFile(programPath),
(await fetchText(`../fixtures/canon/${fileFixtureName}.events`)).trimEnd(),
);
}
const namedParamIniPath = "/work/namedparams.ini";
interp.writeTextFile(namedParamIniPath, await fetchText("../fixtures/ini/namedparams.ini"));
const namedParamPath = "/work/namedparam_semantics.ngc";
interp.writeTextFile(
namedParamPath,
await fetchText("../fixtures/gcode/namedparam_semantics.ngc"),
);
verifyExpectedOutput(
"namedparam_semantics_file",
interp.runFileWithIni(namedParamPath, namedParamIniPath),
(await fetchText("../fixtures/canon/namedparam_semantics.events")).trimEnd(),
);
status.textContent = "browser_interp_smoke=ok";
} catch (error) {
status.textContent = `browser_interp_smoke=fail ${error.stack || error.message}`;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,78 @@
#!/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_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 "browser interpreter 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/interp_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_interp_smoke=ok" "$OUT"; then
echo "browser interpreter smoke failed" >&2
sed -n '1,220p' "$OUT" >&2
exit 1
fi
echo "browser_interp_smoke=ok"