9425 lines
362 KiB
HTML
9425 lines
362 KiB
HTML
<!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 {
|
|
analyzeIniRuntimeBoundaries,
|
|
createLinuxCncIniSdk,
|
|
createLinuxCncInterpSdk,
|
|
planIniFileContextStaging,
|
|
planSimConfigStaging,
|
|
} from "../../runtime/sdk/src/index.js";
|
|
import {
|
|
saveMachineParametersToOpfs,
|
|
} from "../../runtime/opfs/linuxcnc-parameter-bridge.js";
|
|
import {
|
|
loadTextFile,
|
|
saveTextFile,
|
|
} from "../../runtime/opfs/file-service.js";
|
|
import {
|
|
saveMachineToolTableToOpfs,
|
|
} from "../../runtime/opfs/linuxcnc-tool-table-bridge.js";
|
|
import {
|
|
loadMachineSessionFromOpfs,
|
|
} from "../../runtime/opfs/linuxcnc-machine-session-bridge.js";
|
|
import {
|
|
loadMachineTextFiles,
|
|
saveMachineTextFiles,
|
|
} from "../../runtime/opfs/machine-file-store.js";
|
|
import {
|
|
INTERP_BASELINE_INI_FIXTURE,
|
|
INTERP_BROWSER_FILE_FIXTURES,
|
|
INTERP_BROWSER_MDI_FIXTURES,
|
|
INTERP_COORDINATE_OFFSETS_FILE_FIXTURE,
|
|
INTERP_ERROR_FIXTURES,
|
|
INTERP_INI_FIXTURE,
|
|
INTERP_POSITION_PARAMS_FILE_FIXTURE,
|
|
INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE,
|
|
} from "../fixtures/interp-fixture-matrix.mjs";
|
|
|
|
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}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseTsv(text) {
|
|
const [headerLine, ...lines] = text.trim().split("\n");
|
|
const headers = headerLine.split("\t");
|
|
return lines.filter(Boolean).map((line) => {
|
|
const values = line.split("\t");
|
|
return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""]));
|
|
});
|
|
}
|
|
|
|
async function verifyBrowserGeneratedArtifactDocumentationCoverage() {
|
|
const documentationText = [
|
|
await fetchText("../../docs/compatibility-validation.md"),
|
|
await fetchText("../../docs/sim-configs-coverage-matrix.md"),
|
|
await fetchText("../../docs/full-process-boundary-design.md"),
|
|
].join("\n");
|
|
const wasmArtifactNames = [
|
|
"blocked-dependency-summary.tsv",
|
|
"blocked-runtime-promotion-lock.tsv",
|
|
"boundary-phase-completion-summary.tsv",
|
|
"boundary-proof-gates.tsv",
|
|
"boundary-summary.tsv",
|
|
"full-process-boundary-summary.tsv",
|
|
"ini-boundary-summary.tsv",
|
|
"native-proof-alignment-summary.tsv",
|
|
"native-runtime-probe-execution-plan.tsv",
|
|
"native-runtime-probe-pass-evidence-contract.tsv",
|
|
"native-runtime-probe-summary.tsv",
|
|
"next-boundary-recommendations.tsv",
|
|
"next-boundary-worklist.tsv",
|
|
"python-remap-boundary-summary.tsv",
|
|
"python-remap-family-summary.tsv",
|
|
"python-remap-native-runtime-alignment.tsv",
|
|
"python-remap-native-runtime-fixture-plan.tsv",
|
|
"python-remap-native-runtime-probe-gate.tsv",
|
|
"python-remap-native-runtime-readiness.tsv",
|
|
"python-remap-native-runtime-state-plan.tsv",
|
|
"python-remap-runtime-contract.tsv",
|
|
"python-remap-runtime-gates.tsv",
|
|
"runtime-boundary-contract-summary.tsv",
|
|
"runtime-boundary-family-host-readiness.tsv",
|
|
"runtime-boundary-host-preflight.tsv",
|
|
"runtime-boundary-host-readiness-rollup.tsv",
|
|
"runtime-boundary-host-requirement-summary.tsv",
|
|
"runtime-boundary-host-unblock-plan.tsv",
|
|
"runtime-boundary-native-alignment-summary.tsv",
|
|
"runtime-boundary-native-evidence-acceptance-gate.tsv",
|
|
"runtime-boundary-opt-in-probe-dispatch-plan.tsv",
|
|
"runtime-boundary-opt-in-probe-dispatch-rollup.tsv",
|
|
"runtime-boundary-opt-in-probe-skip-evidence-contract.tsv",
|
|
"runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv",
|
|
"runtime-boundary-post-native-pass-gates.tsv",
|
|
"runtime-boundary-promotion-blockers.tsv",
|
|
"runtime-boundary-promotion-readiness.tsv",
|
|
"runtime-probe-gate-alignment.tsv",
|
|
"skip-summary.tsv",
|
|
"summary.tsv",
|
|
"tool-db-process-boundary-summary.tsv",
|
|
"tool-db-process-native-protocol-alignment.tsv",
|
|
"tool-db-process-native-runtime-probe-gate.tsv",
|
|
"tool-db-process-native-runtime-readiness.tsv",
|
|
"tool-db-process-protocol-gates.tsv",
|
|
"tool-db-process-transaction-plan.tsv",
|
|
"user-m-process-boundary-summary.tsv",
|
|
"user-m-process-native-runtime-probe-gate.tsv",
|
|
"user-m-process-native-runtime-readiness.tsv",
|
|
"user-m-process-native-runtime-state-plan.tsv",
|
|
"user-m-process-native-state-alignment.tsv",
|
|
"user-m-process-native-transition-alignment.tsv",
|
|
"user-m-process-state-targets.tsv",
|
|
"user-m-process-transition-plan.tsv",
|
|
].sort();
|
|
const nativeArtifactTokens = [
|
|
"build/native/native-runtime-probe-summary.tsv",
|
|
"build/native/native-source-proof-summary.tsv",
|
|
"build/native/nc-files/summary.tsv",
|
|
"build/native/sim-configs/class-summary.tsv",
|
|
"build/native/sim-configs/path-matrix.tsv",
|
|
"build/native/sim-configs/skipped.tsv",
|
|
"build/native/sim-configs/summary.tsv",
|
|
"build/native/source-probes.tsv",
|
|
];
|
|
|
|
if (wasmArtifactNames.length !== 54 || new Set(wasmArtifactNames).size !== wasmArtifactNames.length) {
|
|
throw new Error("browser_generated_artifact_documentation_coverage: WASM artifact list drift");
|
|
}
|
|
if (nativeArtifactTokens.length !== 8 || new Set(nativeArtifactTokens).size !== nativeArtifactTokens.length) {
|
|
throw new Error("browser_native_artifact_documentation_coverage: native artifact token list drift");
|
|
}
|
|
|
|
const emptyNativeArtifactTokensAllowed = new Set([
|
|
"build/native/sim-configs/skipped.tsv",
|
|
]);
|
|
for (const artifactName of wasmArtifactNames) {
|
|
const artifactText = await fetchText(`../../build/wasm/sim-configs-inventory/${artifactName}`);
|
|
if (!artifactText.includes("\t") || artifactText.trim().split("\n")[0].length === 0) {
|
|
throw new Error(`browser_generated_artifact_fetchability: invalid ${artifactName}`);
|
|
}
|
|
}
|
|
for (const artifactToken of nativeArtifactTokens) {
|
|
const artifactText = await fetchText(`../../${artifactToken}`);
|
|
if (artifactText.length === 0 && emptyNativeArtifactTokensAllowed.has(artifactToken)) {
|
|
continue;
|
|
}
|
|
if (!artifactText.includes("\t") || artifactText.trim().split("\n")[0].length === 0) {
|
|
throw new Error(`browser_native_artifact_fetchability: invalid ${artifactToken}`);
|
|
}
|
|
}
|
|
|
|
for (const artifactName of wasmArtifactNames) {
|
|
if (!documentationText.includes(artifactName)) {
|
|
throw new Error(`browser_generated_artifact_documentation_coverage: missing ${artifactName}`);
|
|
}
|
|
}
|
|
for (const artifactToken of nativeArtifactTokens) {
|
|
if (!documentationText.includes(artifactToken)) {
|
|
throw new Error(`browser_native_artifact_documentation_coverage: missing ${artifactToken}`);
|
|
}
|
|
}
|
|
|
|
return { nativeArtifactTokens, wasmArtifactNames };
|
|
}
|
|
|
|
async function verifyBrowserBoundaryPhaseCompletion() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/boundary-phase-completion-summary.tsv"),
|
|
);
|
|
const expectedCriteria = [
|
|
"vendored_runtime_boundary_reports",
|
|
"hard_block_dependency_evidence",
|
|
"safe_hal_ui_representatives",
|
|
"blocked_families_not_promoted",
|
|
"linuxcnc_runtime_owner_proof",
|
|
"native_source_proof_alignment",
|
|
"runtime_native_alignment_artifacts",
|
|
"native_runtime_probe_summary",
|
|
"runtime_probe_gate_alignment",
|
|
"blocked_runtime_worklist_recommendation_consistency",
|
|
"native_runtime_probe_execution_plan",
|
|
"native_runtime_probe_pass_evidence_contract",
|
|
"blocked_runtime_probe_execution_consistency",
|
|
"runtime_boundary_promotion_readiness",
|
|
"runtime_boundary_promotion_blockers",
|
|
"runtime_boundary_post_native_pass_gates",
|
|
"blocked_runtime_promotion_gate_consistency",
|
|
"runtime_boundary_host_preflight",
|
|
"runtime_boundary_host_requirement_summary",
|
|
"runtime_boundary_host_unblock_plan",
|
|
"runtime_boundary_family_host_readiness",
|
|
"blocked_runtime_host_requirement_consistency",
|
|
"runtime_boundary_host_readiness_rollup",
|
|
"runtime_boundary_opt_in_probe_dispatch_plan",
|
|
"runtime_boundary_opt_in_probe_dispatch_rollup",
|
|
"runtime_boundary_opt_in_probe_skip_evidence_contract",
|
|
"runtime_boundary_opt_in_probe_skip_evidence_rollup",
|
|
"blocked_runtime_rollup_consistency",
|
|
"runtime_boundary_native_evidence_acceptance_gate",
|
|
"blocked_runtime_opt_in_gate_consistency",
|
|
"native_source_proof_runtime_probe_consistency",
|
|
"user_m_transition_contract",
|
|
"user_m_native_runtime_state_plan",
|
|
"user_m_native_runtime_readiness",
|
|
"user_m_native_runtime_probe_gate",
|
|
"tool_db_transaction_contract",
|
|
"tool_db_native_runtime_readiness",
|
|
"tool_db_native_runtime_probe_gate",
|
|
"python_runtime_contract",
|
|
"python_native_runtime_readiness",
|
|
"python_native_runtime_probe_gate",
|
|
"python_native_runtime_state_plan",
|
|
"python_native_runtime_fixture_plan",
|
|
"runtime_contract_summary",
|
|
"runtime_family_contract_alignment_consistency",
|
|
"wasm_inventory_artifact_documentation_coverage",
|
|
"native_artifact_documentation_coverage",
|
|
];
|
|
const actualCriteria = rows.map((row) => row.criterion).join(",");
|
|
if (actualCriteria !== expectedCriteria.join(",")) {
|
|
throw new Error(`browser_boundary_phase_completion: criterion drift ${actualCriteria}`);
|
|
}
|
|
for (const row of rows) {
|
|
if (row.passed !== "1") {
|
|
throw new Error(`${row.criterion}: boundary phase completion failed`);
|
|
}
|
|
if (!/^[1-9][0-9]*$/.test(row.count)) {
|
|
throw new Error(`${row.criterion}: invalid completion count ${row.count}`);
|
|
}
|
|
if (row.evidence === "-") {
|
|
throw new Error(`${row.criterion}: missing completion evidence`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function verifyBrowserBoundaryPhaseCompletionCountParity(completionRows, expectedCounts) {
|
|
const completionByCriterion = new Map(completionRows.map((row) => [row.criterion, row]));
|
|
|
|
for (const [criterion, expectedCount] of expectedCounts) {
|
|
const row = completionByCriterion.get(criterion);
|
|
if (!row) {
|
|
throw new Error(`browser_boundary_phase_completion_count_parity: missing ${criterion}`);
|
|
}
|
|
if (row.passed !== "1") {
|
|
throw new Error(`${criterion}: completion criterion must pass before count parity`);
|
|
}
|
|
if (row.count !== String(expectedCount)) {
|
|
throw new Error(`${criterion}: completion count ${row.count} does not match browser artifact count ${expectedCount}`);
|
|
}
|
|
if (row.evidence === "-" || row.evidence.length < 12) {
|
|
throw new Error(`${criterion}: completion evidence is not reviewable`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserIniBoundarySummary() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/ini-boundary-summary.tsv"),
|
|
);
|
|
const byIni = new Map(rows.map((row) => [row.ini, row]));
|
|
const requiredVendoredInis = new Map([
|
|
["axis/db_demo/db_nonran.ini", "L4-TOOL-DB"],
|
|
["axis/gladevcp/gladevcp_panel.ini", "-"],
|
|
["axis/vismach/melfa-sim/melfa.ini", "-"],
|
|
["axis/vismach/millturn/millturn.ini", "L4-USER-M-PROCESS"],
|
|
["axis/vismach/puma/puma_cube.ini", "-"],
|
|
["woodpecker/woodpecker.ini", "-"],
|
|
]);
|
|
|
|
for (const [ini, expectedRecommendation] of requiredVendoredInis) {
|
|
const row = byIni.get(ini);
|
|
if (!row) {
|
|
throw new Error(`browser_ini_boundary_summary: missing ${ini}`);
|
|
}
|
|
if (row.vendored !== "1" || row.report_available !== "1") {
|
|
throw new Error(`${ini}: vendored INI boundary report unavailable`);
|
|
}
|
|
if (!row.recommended_blocked.split(",").includes(expectedRecommendation)) {
|
|
throw new Error(`${ini}: recommended block drift ${row.recommended_blocked}`);
|
|
}
|
|
if (row.hard_block_policy_aligned !== "1") {
|
|
throw new Error(`${ini}: hard-block policy alignment failed`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserBoundaryProofGates() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/boundary-proof-gates.tsv"),
|
|
);
|
|
const browserRows = rows.filter((row) => row.proof_layer === "browser");
|
|
const browserByTarget = new Map(browserRows.map((row) => [row.target, row]));
|
|
const requiredBlockedTargets = new Map([
|
|
[
|
|
"axis/vismach/millturn/example.ngc",
|
|
{
|
|
blocked: "L4-USER-M-PROCESS",
|
|
proof: "no_full_process_claim",
|
|
nextAction: "design_m128_m129_linuxcnc_state_boundary",
|
|
},
|
|
],
|
|
[
|
|
"axis/db_demo/base.ngc",
|
|
{
|
|
blocked: "L4-TOOL-DB",
|
|
proof: "opfs_persistence_only",
|
|
nextAction: "design_tooldata_db_protocol_boundary",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (browserRows.length === 0) {
|
|
throw new Error("browser_boundary_proof_gates: missing browser proof rows");
|
|
}
|
|
|
|
for (const [target, expected] of requiredBlockedTargets) {
|
|
const row = browserByTarget.get(target);
|
|
if (!row) {
|
|
throw new Error(`browser_boundary_proof_gates: missing ${target}`);
|
|
}
|
|
if (row.blocked !== expected.blocked) {
|
|
throw new Error(`${target}: blocked gate drift ${row.blocked}`);
|
|
}
|
|
if (row.proof_status !== "pending") {
|
|
throw new Error(`${target}: browser proof gate must remain pending`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${target}: browser proof gate must not enable execution or promotion`);
|
|
}
|
|
if (!row.required_proof.includes(expected.proof)) {
|
|
throw new Error(`${target}: browser proof gate missing ${expected.proof}`);
|
|
}
|
|
if (row.next_action !== expected.nextAction) {
|
|
throw new Error(`${target}: next action drift ${row.next_action}`);
|
|
}
|
|
}
|
|
|
|
for (const row of browserRows.filter((entry) => entry.boundary_kind === "python_runtime")) {
|
|
if (row.blocked !== "L4-PYTHON-REMAP") {
|
|
throw new Error(`${row.target}: Python browser proof gate blocked kind drift`);
|
|
}
|
|
if (row.proof_status !== "pending") {
|
|
throw new Error(`${row.target}: Python browser proof gate must remain pending`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.target}: Python browser proof gate must stay disabled`);
|
|
}
|
|
if (!row.required_proof.includes("no_full_process_claim")) {
|
|
throw new Error(`${row.target}: Python browser proof gate must avoid full-process claim`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserNextBoundaryWorklist() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/next-boundary-worklist.tsv"),
|
|
);
|
|
const expectedFirstRows = [
|
|
["external_user_m_process", "axis/vismach/millturn/example.ngc", "L4-USER-M-PROCESS"],
|
|
["tool_database_process", "axis/db_demo/base.ngc", "L4-TOOL-DB"],
|
|
];
|
|
|
|
if (rows.length < expectedFirstRows.length) {
|
|
throw new Error(`browser_next_boundary_worklist: too few rows ${rows.length}`);
|
|
}
|
|
const priorities = rows.map((row) => Number(row.priority));
|
|
for (let index = 1; index < priorities.length; index += 1) {
|
|
if (priorities[index] < priorities[index - 1]) {
|
|
throw new Error("browser_next_boundary_worklist: priority order drift");
|
|
}
|
|
}
|
|
|
|
for (const [index, expected] of expectedFirstRows.entries()) {
|
|
const row = rows[index];
|
|
if (
|
|
row.boundary_kind !== expected[0] ||
|
|
row.target !== expected[1] ||
|
|
row.blocked !== expected[2]
|
|
) {
|
|
throw new Error(`browser_next_boundary_worklist: row ${index + 1} identity drift`);
|
|
}
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.target}: next boundary worklist must remain disabled`);
|
|
}
|
|
if (
|
|
row.linuxcnc_owner === "-" ||
|
|
row.runtime_owner_evidence === "-" ||
|
|
row.required_native_proof === "-" ||
|
|
row.required_node_proof === "-" ||
|
|
row.required_browser_proof === "-" ||
|
|
row.next_action === "-"
|
|
) {
|
|
throw new Error(`${row.target}: next boundary worklist lacks traceability`);
|
|
}
|
|
if (row.boundary_kind === "python_runtime") {
|
|
if (
|
|
row.blocked !== "L4-PYTHON-REMAP" ||
|
|
row.design_status !== "inventory_only" ||
|
|
row.next_action !== "design_linuxcnc_python_runtime_boundary" ||
|
|
!row.notes.includes("dependency_inventory_only_no_execution")
|
|
) {
|
|
throw new Error(`${row.target}: Python worklist row must remain inventory-only`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function verifyBrowserNextBoundaryWorklistProofGateParity({
|
|
worklistRows,
|
|
proofGateRows,
|
|
promotionLockRows,
|
|
}) {
|
|
const expectedLayers = ["browser", "native", "node"];
|
|
const proofRowsByTarget = new Map();
|
|
for (const row of proofGateRows) {
|
|
const targetRows = proofRowsByTarget.get(row.target) || [];
|
|
targetRows.push(row);
|
|
proofRowsByTarget.set(row.target, targetRows);
|
|
}
|
|
const lockByTarget = new Map(promotionLockRows.map((row) => [row.target, row]));
|
|
|
|
if (proofGateRows.length !== worklistRows.length * expectedLayers.length) {
|
|
throw new Error("browser_next_boundary_worklist_proof_gate_parity: proof gate count drift");
|
|
}
|
|
|
|
for (const worklist of worklistRows) {
|
|
const proofRows = proofRowsByTarget.get(worklist.target) || [];
|
|
const layers = proofRows.map((row) => row.proof_layer).sort();
|
|
if (layers.join(",") !== expectedLayers.join(",")) {
|
|
throw new Error(`${worklist.target}: proof gate layer set drift`);
|
|
}
|
|
const lock = lockByTarget.get(worklist.target);
|
|
if (!lock) {
|
|
throw new Error(`${worklist.target}: promotion lock row missing`);
|
|
}
|
|
if (
|
|
lock.boundary_kind !== worklist.boundary_kind ||
|
|
lock.blocked !== worklist.blocked ||
|
|
lock.design_status !== worklist.design_status ||
|
|
lock.next_action !== worklist.next_action ||
|
|
lock.lock_active !== "1"
|
|
) {
|
|
throw new Error(`${worklist.target}: worklist/promotion lock drift`);
|
|
}
|
|
if (lock.execution_enabled !== "0" || lock.promotion_allowed !== "0") {
|
|
throw new Error(`${worklist.target}: promotion lock must remain disabled`);
|
|
}
|
|
|
|
for (const proof of proofRows) {
|
|
if (
|
|
proof.priority !== worklist.priority ||
|
|
proof.boundary_kind !== worklist.boundary_kind ||
|
|
proof.blocked !== worklist.blocked ||
|
|
proof.design_status !== worklist.design_status ||
|
|
proof.linuxcnc_owner !== worklist.linuxcnc_owner ||
|
|
proof.runtime_owner_evidence !== worklist.runtime_owner_evidence ||
|
|
proof.next_action !== worklist.next_action
|
|
) {
|
|
throw new Error(`${worklist.target}/${proof.proof_layer}: worklist/proof gate metadata drift`);
|
|
}
|
|
if (
|
|
proof.proof_status !== "pending" ||
|
|
proof.execution_enabled !== "0" ||
|
|
proof.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${worklist.target}/${proof.proof_layer}: proof gate must remain pending and disabled`);
|
|
}
|
|
if (proof.proof_layer === "native" && proof.required_proof !== worklist.required_native_proof) {
|
|
throw new Error(`${worklist.target}: native proof gate drift`);
|
|
}
|
|
if (proof.proof_layer === "node" && proof.required_proof !== worklist.required_node_proof) {
|
|
throw new Error(`${worklist.target}: Node proof gate drift`);
|
|
}
|
|
if (
|
|
proof.proof_layer === "browser" &&
|
|
!proof.required_proof.split(",").includes(worklist.required_browser_proof.split(",")[0])
|
|
) {
|
|
throw new Error(`${worklist.target}: browser proof gate drift`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserBoundarySummary() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/boundary-summary.tsv"),
|
|
);
|
|
const byPath = new Map(rows.map((row) => [row.path, row]));
|
|
const safeRepresentatives = new Map([
|
|
[
|
|
"axis/gladevcp/probe.ngc",
|
|
{
|
|
dependencies: "hal_process,python_runtime,ui_process",
|
|
hal_process: "1",
|
|
ui_process: "1",
|
|
halui_mdi_process: "0",
|
|
python_process: "1",
|
|
python_remap_process: "0",
|
|
},
|
|
],
|
|
[
|
|
"axis/vismach/melfa-sim/example.ngc",
|
|
{
|
|
dependencies: "hal_process,halui_mdi_process,ui_process",
|
|
hal_process: "1",
|
|
ui_process: "1",
|
|
halui_mdi_process: "1",
|
|
python_process: "0",
|
|
python_remap_process: "0",
|
|
},
|
|
],
|
|
[
|
|
"axis/vismach/puma/puma_cube.ngc",
|
|
{
|
|
dependencies: "hal_process,halui_mdi_process,ui_process",
|
|
hal_process: "1",
|
|
ui_process: "1",
|
|
halui_mdi_process: "1",
|
|
python_process: "0",
|
|
python_remap_process: "0",
|
|
},
|
|
],
|
|
[
|
|
"woodpecker/on_abort.ngc",
|
|
{
|
|
dependencies: "hal_process,ui_process",
|
|
hal_process: "1",
|
|
ui_process: "1",
|
|
halui_mdi_process: "0",
|
|
python_process: "0",
|
|
python_remap_process: "0",
|
|
},
|
|
],
|
|
]);
|
|
|
|
for (const [path, expected] of safeRepresentatives) {
|
|
const row = byPath.get(path);
|
|
if (!row) {
|
|
throw new Error(`browser_boundary_summary: missing safe representative ${path}`);
|
|
}
|
|
if (row.blocked !== "-" || row.recommended_blocked !== "-") {
|
|
throw new Error(`${path}: safe representative must not be hard-blocked`);
|
|
}
|
|
if (row.dependencies !== expected.dependencies) {
|
|
throw new Error(`${path}: dependency drift ${row.dependencies}`);
|
|
}
|
|
for (const field of [
|
|
"hal_process",
|
|
"ui_process",
|
|
"halui_mdi_process",
|
|
"python_process",
|
|
"python_remap_process",
|
|
]) {
|
|
if (row[field] !== expected[field]) {
|
|
throw new Error(`${path}: ${field} drift ${row[field]}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
const millturn = byPath.get("axis/vismach/millturn/example.ngc");
|
|
if (!millturn) {
|
|
throw new Error("browser_boundary_summary: missing millturn blocked row");
|
|
}
|
|
if (
|
|
millturn.blocked !== "L4-USER-M-PROCESS" ||
|
|
millturn.recommended_blocked !== "L4-USER-M-PROCESS"
|
|
) {
|
|
throw new Error("millturn boundary summary must remain L4-USER-M-PROCESS");
|
|
}
|
|
if (
|
|
millturn.user_m_execution_codes !== "M128,M129" ||
|
|
millturn.user_m_unstaged_execution_codes !== "M128,M129"
|
|
) {
|
|
throw new Error("millturn boundary summary must keep M128/M129 unstaged");
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserUserMTransitionPlan() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-transition-plan.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"M128",
|
|
{
|
|
remapCode: "M428",
|
|
switchkinsTarget: "0",
|
|
activeG5x: "G59.1",
|
|
workOffsetPocket: "P7",
|
|
guardPin: "kinstype.is-0",
|
|
stateMode: "mill",
|
|
},
|
|
],
|
|
[
|
|
"M129",
|
|
{
|
|
remapCode: "M429",
|
|
switchkinsTarget: "1",
|
|
activeG5x: "G59.2",
|
|
workOffsetPocket: "P8",
|
|
guardPin: "kinstype.is-1",
|
|
stateMode: "turn",
|
|
},
|
|
],
|
|
]);
|
|
const byCode = new Map(rows.map((row) => [row.user_m_code, row]));
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_user_m_transition_plan: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const [code, expectedRow] of expected) {
|
|
const row = byCode.get(code);
|
|
if (!row) {
|
|
throw new Error(`browser_user_m_transition_plan: missing ${code}`);
|
|
}
|
|
if (
|
|
row.path !== "axis/vismach/millturn/example.ngc" ||
|
|
row.blocked !== "L4-USER-M-PROCESS" ||
|
|
row.remap_code !== expectedRow.remapCode
|
|
) {
|
|
throw new Error(`${code}: transition plan identity drift`);
|
|
}
|
|
if (
|
|
row.switchkins_output_pin !== "motion.analog-out-03" ||
|
|
row.switchkins_target !== expectedRow.switchkinsTarget ||
|
|
row.active_g5x !== expectedRow.activeG5x ||
|
|
row.work_offset_pocket !== expectedRow.workOffsetPocket ||
|
|
row.guard_pin !== expectedRow.guardPin ||
|
|
row.state_mode !== expectedRow.stateMode
|
|
) {
|
|
throw new Error(`${code}: transition plan state drift`);
|
|
}
|
|
if (row.state_target_count !== "12") {
|
|
throw new Error(`${code}: transition plan state target count drift`);
|
|
}
|
|
for (const pin of ["ini.x.min_limit", "ini.y.min_velocity", "ini.z.max_acceleration"]) {
|
|
if (!row.expected_state_pins.includes(pin)) {
|
|
throw new Error(`${code}: transition plan missing ${pin}`);
|
|
}
|
|
}
|
|
if (
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${code}: transition plan must remain pending and disabled`);
|
|
}
|
|
if (!row.notes.includes("no_process_execution")) {
|
|
throw new Error(`${code}: transition plan must record non-execution policy`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserUserMNativeTransitionAlignment() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-native-transition-alignment.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"M428",
|
|
{
|
|
userMCode: "M128",
|
|
switchkinsTarget: "0",
|
|
activeG5x: "G59.1",
|
|
workOffsetPocket: "P7",
|
|
},
|
|
],
|
|
[
|
|
"M429",
|
|
{
|
|
userMCode: "M129",
|
|
switchkinsTarget: "1",
|
|
activeG5x: "G59.2",
|
|
workOffsetPocket: "P8",
|
|
},
|
|
],
|
|
]);
|
|
const byRemap = new Map(rows.map((row) => [row.remap_code, row]));
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_user_m_native_transition_alignment: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const [remapCode, expectedRow] of expected) {
|
|
const row = byRemap.get(remapCode);
|
|
if (!row) {
|
|
throw new Error(`browser_user_m_native_transition_alignment: missing ${remapCode}`);
|
|
}
|
|
if (
|
|
row.path !== "axis/vismach/millturn/example.ngc" ||
|
|
row.blocked !== "L4-USER-M-PROCESS" ||
|
|
row.user_m_code !== expectedRow.userMCode
|
|
) {
|
|
throw new Error(`${remapCode}: native transition alignment identity drift`);
|
|
}
|
|
if (
|
|
row.switchkins_output_pin !== "motion.analog-out-03" ||
|
|
row.native_switchkins_output_pin !== row.switchkins_output_pin ||
|
|
row.switchkins_target !== expectedRow.switchkinsTarget ||
|
|
row.native_switchkins_target !== row.switchkins_target ||
|
|
row.active_g5x !== expectedRow.activeG5x ||
|
|
row.native_active_g5x !== row.active_g5x ||
|
|
row.work_offset_pocket !== expectedRow.workOffsetPocket ||
|
|
row.native_work_offset_pocket !== row.work_offset_pocket
|
|
) {
|
|
throw new Error(`${remapCode}: native transition alignment state drift`);
|
|
}
|
|
if (
|
|
row.native_call_key !== `${remapCode}_calls_${expectedRow.userMCode}` ||
|
|
row.native_call_value !== "1" ||
|
|
row.native_transition_key !== `${remapCode}_source_transition_from_linuxcnc` ||
|
|
row.native_transition_value !== "1" ||
|
|
row.alignment_ok !== "1"
|
|
) {
|
|
throw new Error(`${remapCode}: native transition source proof drift`);
|
|
}
|
|
if (
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${remapCode}: native transition alignment must remain pending and disabled`);
|
|
}
|
|
if (!row.notes.includes("no_process_execution")) {
|
|
throw new Error(`${remapCode}: native transition alignment must record non-execution policy`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserUserMNativeRuntimeStatePlan() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-native-runtime-state-plan.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
["M428", { userMCode: "M128", target: "0", g5x: "G59.1", pocket: "P7" }],
|
|
["M429", { userMCode: "M129", target: "1", g5x: "G59.2", pocket: "P8" }],
|
|
]);
|
|
const byRemap = new Map(rows.map((row) => [row.remap_code, row]));
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_user_m_native_runtime_state_plan: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const [remapCode, expectedRow] of expected) {
|
|
const row = byRemap.get(remapCode);
|
|
if (!row) {
|
|
throw new Error(`browser_user_m_native_runtime_state_plan: missing ${remapCode}`);
|
|
}
|
|
if (
|
|
row.path !== "axis/vismach/millturn/example.ngc" ||
|
|
row.blocked !== "L4-USER-M-PROCESS" ||
|
|
row.user_m_code !== expectedRow.userMCode ||
|
|
row.required_runtime !== "linuxcnc_task_hal_tcl_user_m_process"
|
|
) {
|
|
throw new Error(`${remapCode}: runtime state plan identity drift`);
|
|
}
|
|
for (const token of ["INI_FILE_NAME", "motion.switchkins-type", "kinstype.is-0", "kinstype.is-1", "ini.[xyz].*"]) {
|
|
if (!row.required_environment.includes(token)) {
|
|
throw new Error(`${remapCode}: runtime state plan missing environment ${token}`);
|
|
}
|
|
}
|
|
if (
|
|
row.switchkins_output_pin !== "motion.analog-out-03" ||
|
|
row.switchkins_target !== expectedRow.target ||
|
|
row.active_g5x !== expectedRow.g5x ||
|
|
row.work_offset_pocket !== expectedRow.pocket ||
|
|
row.state_target_count !== "12" ||
|
|
row.source_alignment_ok !== "1"
|
|
) {
|
|
throw new Error(`${remapCode}: runtime state plan state drift`);
|
|
}
|
|
if (
|
|
!row.source_alignment_artifacts.includes("user-m-process-native-transition-alignment.tsv") ||
|
|
!row.source_alignment_artifacts.includes("user-m-process-native-state-alignment.tsv")
|
|
) {
|
|
throw new Error(`${remapCode}: runtime state plan missing source alignment artifacts`);
|
|
}
|
|
if (
|
|
row.native_runtime_status !== "pending_native_hal_tcl_process_probe" ||
|
|
row.proof_kind !== "native_runtime_state_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${remapCode}: runtime state plan must remain pending and disabled`);
|
|
}
|
|
if (!row.notes.includes("keep_millturn_blocked")) {
|
|
throw new Error(`${remapCode}: runtime state plan must keep millturn blocked`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserUserMNativeRuntimeReadiness() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-native-runtime-readiness.tsv"),
|
|
);
|
|
const expectedRequirements = ["tclsh", "halrun", "halcmd", "linuxcnc"];
|
|
|
|
if (rows.map((row) => row.requirement).join(",") !== expectedRequirements.join(",")) {
|
|
throw new Error("browser_user_m_native_runtime_readiness: requirement order drift");
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (
|
|
row.path !== "axis/vismach/millturn/example.ngc" ||
|
|
row.blocked !== "L4-USER-M-PROCESS" ||
|
|
row.required_runtime !== "linuxcnc_task_hal_tcl_user_m_process" ||
|
|
row.requirement_kind !== "host_command"
|
|
) {
|
|
throw new Error(`${row.requirement}: runtime readiness identity drift`);
|
|
}
|
|
if (!["0", "1"].includes(row.available)) {
|
|
throw new Error(`${row.requirement}: invalid readiness availability ${row.available}`);
|
|
}
|
|
if (row.available === "1" && row.evidence === "-") {
|
|
throw new Error(`${row.requirement}: available runtime command lacks evidence`);
|
|
}
|
|
if (
|
|
![
|
|
"blocked_missing_host_runtime",
|
|
"host_runtime_available_probe_not_implemented",
|
|
].includes(row.native_runtime_status)
|
|
) {
|
|
throw new Error(`${row.requirement}: invalid runtime readiness status`);
|
|
}
|
|
if (
|
|
row.proof_kind !== "native_runtime_state_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.requirement}: runtime readiness must remain pending and disabled`);
|
|
}
|
|
if (
|
|
!row.notes.includes("keep_millturn_blocked") &&
|
|
!row.notes.includes("probe_still_not_implemented")
|
|
) {
|
|
throw new Error(`${row.requirement}: runtime readiness must keep blocked state`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserUserMNativeRuntimeProbeGate() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/user-m-process-native-runtime-probe-gate.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
["M428", "M128"],
|
|
["M429", "M129"],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_user_m_native_runtime_probe_gate: row count drift ${rows.length}`);
|
|
}
|
|
|
|
const allRuntimeReady = rows.every((row) => row.runtime_ready === "1");
|
|
for (const row of rows) {
|
|
if (
|
|
row.path !== "axis/vismach/millturn/example.ngc" ||
|
|
row.ini !== "axis/vismach/millturn/millturn.ini" ||
|
|
row.blocked !== "L4-USER-M-PROCESS" ||
|
|
row.required_runtime !== "linuxcnc_task_hal_tcl_user_m_process"
|
|
) {
|
|
throw new Error(`${row.remap_code}: runtime probe gate identity drift`);
|
|
}
|
|
if (expected.get(row.remap_code) !== row.user_m_code) {
|
|
throw new Error(`${row.remap_code}: runtime probe gate user-M drift`);
|
|
}
|
|
for (const requirement of ["tclsh", "halrun", "halcmd", "linuxcnc"]) {
|
|
if (!row.runtime_requirements.includes(`${requirement}:`)) {
|
|
throw new Error(`${row.remap_code}: runtime probe gate missing ${requirement}`);
|
|
}
|
|
}
|
|
if (
|
|
row.source_proof_ready !== "1" ||
|
|
row.required_native_proof !== "native_runtime_state_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.remap_code}: runtime probe gate must remain source-ready, pending, and disabled`);
|
|
}
|
|
if (allRuntimeReady) {
|
|
if (
|
|
row.missing_requirements !== "-" ||
|
|
row.gate_status !== "ready_to_implement_probe" ||
|
|
!row.notes.includes("write_native_hal_tcl_state_probe")
|
|
) {
|
|
throw new Error(`${row.remap_code}: ready runtime probe gate drift`);
|
|
}
|
|
} else {
|
|
if (
|
|
row.missing_requirements === "-" ||
|
|
row.gate_status !== "blocked_missing_host_runtime" ||
|
|
!row.notes.includes("keep_millturn_blocked")
|
|
) {
|
|
throw new Error(`${row.remap_code}: missing-runtime probe gate drift`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserToolDbTransactionPlan() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/tool-db-process-transaction-plan.tsv"),
|
|
);
|
|
const expectedPhases = [
|
|
"startup_handshake",
|
|
"initial_get_all",
|
|
"spindle_load_notify",
|
|
"tool_offset_notify",
|
|
"spindle_unload_notify",
|
|
];
|
|
const expectedProtocol = new Map([
|
|
["startup_handshake", "startup:expect_reply=v2.1"],
|
|
["initial_get_all", "request:g:get_all_until_FINI"],
|
|
["spindle_load_notify", "notify:l:SPINDLE_LOAD"],
|
|
["tool_offset_notify", "notify:p:TOOL_OFFSET"],
|
|
["spindle_unload_notify", "notify:u:SPINDLE_UNLOAD"],
|
|
]);
|
|
const expectedCallback = new Map([
|
|
["startup_handshake", "registration:tooldb_callbacks_tools_loop"],
|
|
["initial_get_all", "g:user_get_tool"],
|
|
["spindle_load_notify", "l:user_load_spindle_nonran_tc"],
|
|
["tool_offset_notify", "p:user_put_tool"],
|
|
["spindle_unload_notify", "u:user_unload_spindle_nonran_tc"],
|
|
]);
|
|
|
|
if (rows.length !== expectedPhases.length) {
|
|
throw new Error(`browser_tool_db_transaction_plan: row count drift ${rows.length}`);
|
|
}
|
|
const actualPhases = rows.map((row) => row.transaction_phase).join(",");
|
|
if (actualPhases !== expectedPhases.join(",")) {
|
|
throw new Error(`browser_tool_db_transaction_plan: phase order drift ${actualPhases}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (
|
|
row.path !== "axis/db_demo/base.ngc" ||
|
|
row.blocked !== "L4-TOOL-DB" ||
|
|
row.db_program !== "./db_nonran.py"
|
|
) {
|
|
throw new Error(`${row.transaction_phase}: tool DB transaction identity drift`);
|
|
}
|
|
if (row.protocol_message !== expectedProtocol.get(row.transaction_phase)) {
|
|
throw new Error(`${row.transaction_phase}: tool DB protocol message drift`);
|
|
}
|
|
if (row.db_program_callback !== expectedCallback.get(row.transaction_phase)) {
|
|
throw new Error(`${row.transaction_phase}: tool DB callback drift`);
|
|
}
|
|
if (row.state_targets === "-") {
|
|
throw new Error(`${row.transaction_phase}: missing tool DB state targets`);
|
|
}
|
|
if (
|
|
row.required_native_proof === "-" ||
|
|
row.required_node_proof === "-" ||
|
|
row.required_browser_proof === "-"
|
|
) {
|
|
throw new Error(`${row.transaction_phase}: missing tool DB required proof`);
|
|
}
|
|
if (
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.transaction_phase}: tool DB transaction must remain pending and disabled`);
|
|
}
|
|
if (!row.notes.includes("no_db_process_execution") || !row.notes.includes("no_tool_table_fallback")) {
|
|
throw new Error(`${row.transaction_phase}: tool DB transaction must record blocked execution policy`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserToolDbNativeRuntimeReadiness() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/tool-db-process-native-runtime-readiness.tsv"),
|
|
);
|
|
const expectedRequirements = [
|
|
"python3",
|
|
"linuxcnc",
|
|
"milltask",
|
|
"halcmd",
|
|
"axis/db_demo/db_nonran.py",
|
|
"linuxcnc.so",
|
|
"tooldb.py",
|
|
];
|
|
|
|
if (rows.map((row) => row.requirement).join(",") !== expectedRequirements.join(",")) {
|
|
throw new Error("browser_tool_db_native_runtime_readiness: requirement order drift");
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (
|
|
row.path !== "axis/db_demo/base.ngc" ||
|
|
row.blocked !== "L4-TOOL-DB" ||
|
|
row.required_runtime !== "linuxcnc_tooldata_db_process" ||
|
|
row.db_program !== "./db_nonran.py"
|
|
) {
|
|
throw new Error(`${row.requirement}: tool DB readiness identity drift`);
|
|
}
|
|
if (!["host_command", "source_config_file", "linuxcnc_python_module"].includes(row.requirement_kind)) {
|
|
throw new Error(`${row.requirement}: invalid readiness kind ${row.requirement_kind}`);
|
|
}
|
|
if (!["0", "1"].includes(row.available)) {
|
|
throw new Error(`${row.requirement}: invalid readiness availability ${row.available}`);
|
|
}
|
|
if (row.available === "1" && row.evidence === "-") {
|
|
throw new Error(`${row.requirement}: available tool DB requirement lacks evidence`);
|
|
}
|
|
if (
|
|
![
|
|
"blocked_missing_host_runtime",
|
|
"host_runtime_available_probe_not_implemented",
|
|
].includes(row.native_runtime_status)
|
|
) {
|
|
throw new Error(`${row.requirement}: invalid tool DB runtime readiness status`);
|
|
}
|
|
if (
|
|
row.proof_kind !== "native_db_process_protocol_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.requirement}: tool DB readiness must remain pending and disabled`);
|
|
}
|
|
if (
|
|
!row.notes.includes("keep_tool_db_blocked") &&
|
|
!row.notes.includes("probe_still_not_implemented")
|
|
) {
|
|
throw new Error(`${row.requirement}: tool DB readiness must keep blocked state`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserToolDbNativeRuntimeProbeGate() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/tool-db-process-native-runtime-probe-gate.tsv"),
|
|
);
|
|
if (rows.length !== 1) {
|
|
throw new Error(`browser_tool_db_native_runtime_probe_gate: row count drift ${rows.length}`);
|
|
}
|
|
|
|
const row = rows[0];
|
|
if (
|
|
row.path !== "axis/db_demo/base.ngc" ||
|
|
row.ini !== "axis/db_demo/db_nonran.ini" ||
|
|
row.blocked !== "L4-TOOL-DB" ||
|
|
row.db_program !== "./db_nonran.py" ||
|
|
row.required_runtime !== "linuxcnc_tooldata_db_process"
|
|
) {
|
|
throw new Error("tool DB runtime probe gate identity drift");
|
|
}
|
|
if (row.python3_sufficient !== "0" || row.tool_table_fallback_sufficient !== "0") {
|
|
throw new Error("tool DB runtime probe gate must reject python3-only and tbl fallback proof");
|
|
}
|
|
for (const field of [
|
|
"python3_available",
|
|
"db_program_source_ready",
|
|
"linuxcnc_python_modules_ready",
|
|
"protocol_contract_ready",
|
|
"runtime_ready",
|
|
"source_proof_ready",
|
|
]) {
|
|
if (!["0", "1"].includes(row[field])) {
|
|
throw new Error(`tool DB runtime probe gate invalid ${field} ${row[field]}`);
|
|
}
|
|
}
|
|
if (
|
|
row.db_program_source_ready !== "1" ||
|
|
row.linuxcnc_python_modules_ready !== "1" ||
|
|
row.protocol_contract_ready !== "1" ||
|
|
row.source_proof_ready !== "1"
|
|
) {
|
|
throw new Error("tool DB runtime probe gate source readiness drift");
|
|
}
|
|
for (const requirement of [
|
|
"python3",
|
|
"linuxcnc",
|
|
"milltask",
|
|
"halcmd",
|
|
"axis/db_demo/db_nonran.py",
|
|
"linuxcnc.so",
|
|
"tooldb.py",
|
|
]) {
|
|
if (!row.runtime_requirements.includes(`${requirement}:`)) {
|
|
throw new Error(`tool DB runtime probe gate missing ${requirement}`);
|
|
}
|
|
}
|
|
for (const phase of [
|
|
"startup_handshake",
|
|
"initial_get_all",
|
|
"spindle_load_notify",
|
|
"tool_offset_notify",
|
|
"spindle_unload_notify",
|
|
]) {
|
|
if (!row.protocol_transactions.includes(`${phase}:`)) {
|
|
throw new Error(`tool DB runtime probe gate missing transaction ${phase}`);
|
|
}
|
|
}
|
|
if (
|
|
row.required_native_proof !== "native_db_process_protocol_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error("tool DB runtime probe gate must remain pending and disabled");
|
|
}
|
|
if (!row.notes.includes("fallback_not_sufficient")) {
|
|
throw new Error("tool DB runtime probe gate must record fallback insufficiency");
|
|
}
|
|
if (row.runtime_ready === "1") {
|
|
if (
|
|
row.missing_requirements !== "-" ||
|
|
row.gate_status !== "ready_to_implement_protocol_probe"
|
|
) {
|
|
throw new Error("ready tool DB runtime probe gate drift");
|
|
}
|
|
} else if (
|
|
row.missing_requirements === "-" ||
|
|
row.gate_status !== "blocked_missing_host_runtime"
|
|
) {
|
|
throw new Error("missing-runtime tool DB probe gate drift");
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserPythonRuntimeContract() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-runtime-contract.tsv"),
|
|
);
|
|
const requiredFamilies = new Set([
|
|
"axis/laser",
|
|
"axis/remap/extend-builtins/nc_files",
|
|
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos",
|
|
"gmoccapy/macros",
|
|
]);
|
|
const phases = [
|
|
"initialize_python",
|
|
"apply_ini_python_path",
|
|
"execute_toplevel",
|
|
"callable_lookup",
|
|
"pycall_dispatch",
|
|
"callable_invoke",
|
|
"remap_phase_dispatch",
|
|
];
|
|
|
|
if (rows.length < requiredFamilies.size) {
|
|
throw new Error(`browser_python_runtime_contract: too few rows ${rows.length}`);
|
|
}
|
|
|
|
const byFamily = new Map(rows.map((row) => [row.family, row]));
|
|
for (const family of requiredFamilies) {
|
|
if (!byFamily.has(family)) {
|
|
throw new Error(`browser_python_runtime_contract: missing ${family}`);
|
|
}
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (row.blocked !== "L4-PYTHON-REMAP" || row.design_status !== "inventory_only") {
|
|
throw new Error(`${row.family}: Python runtime contract identity drift`);
|
|
}
|
|
if (row.python_modules === "-" || row.runtime_owner_evidence === "-") {
|
|
throw new Error(`${row.family}: Python runtime contract lacks dependency evidence`);
|
|
}
|
|
for (const phase of phases) {
|
|
if (!row.runtime_phases.includes(phase)) {
|
|
throw new Error(`${row.family}: Python runtime contract missing phase ${phase}`);
|
|
}
|
|
}
|
|
if (
|
|
row.required_native_proof !== "python_runtime_owner_and_fixture" ||
|
|
row.required_node_proof !== "linuxcnc_python_runtime_boundary_not_js_semantics" ||
|
|
row.required_browser_proof !== "browser_after_node_python_boundary_no_full_process_claim"
|
|
) {
|
|
throw new Error(`${row.family}: Python runtime contract proof drift`);
|
|
}
|
|
if (
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.family}: Python runtime contract must remain pending and disabled`);
|
|
}
|
|
if (!row.notes.includes("python_runtime_contract_only_no_execution")) {
|
|
throw new Error(`${row.family}: Python runtime contract must record non-execution policy`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserPythonNativeRuntimeReadiness() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-native-runtime-readiness.tsv"),
|
|
);
|
|
const requiredFamilies = new Set([
|
|
"axis/laser",
|
|
"axis/remap/extend-builtins/nc_files",
|
|
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos",
|
|
"gmoccapy/macros",
|
|
]);
|
|
|
|
if (rows.length <= requiredFamilies.size * 4) {
|
|
throw new Error(`browser_python_native_runtime_readiness: too few rows ${rows.length}`);
|
|
}
|
|
|
|
const byFamily = new Map();
|
|
for (const row of rows) {
|
|
const familyRows = byFamily.get(row.family) || [];
|
|
familyRows.push(row);
|
|
byFamily.set(row.family, familyRows);
|
|
|
|
if (row.blocked !== "L4-PYTHON-REMAP") {
|
|
throw new Error(`${row.family}: Python readiness blocked kind drift`);
|
|
}
|
|
if (row.required_runtime !== "linuxcnc_python_remap_runtime") {
|
|
throw new Error(`${row.family}: Python readiness runtime drift`);
|
|
}
|
|
if (
|
|
![
|
|
"host_command",
|
|
"linuxcnc_source_file",
|
|
"source_config_python_module",
|
|
].includes(row.requirement_kind)
|
|
) {
|
|
throw new Error(`${row.family}: invalid Python readiness kind ${row.requirement_kind}`);
|
|
}
|
|
if (!["0", "1"].includes(row.available)) {
|
|
throw new Error(`${row.family}: invalid Python readiness availability`);
|
|
}
|
|
if (row.available === "1" && row.evidence === "-") {
|
|
throw new Error(`${row.family}: available Python readiness row lacks evidence`);
|
|
}
|
|
if (
|
|
![
|
|
"blocked_missing_host_runtime",
|
|
"host_runtime_available_probe_not_implemented",
|
|
].includes(row.native_runtime_status)
|
|
) {
|
|
throw new Error(`${row.family}: invalid Python readiness runtime status`);
|
|
}
|
|
if (
|
|
row.proof_kind !== "native_python_runtime_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.family}: Python readiness must remain pending and disabled`);
|
|
}
|
|
if (
|
|
!row.notes.includes("keep_python_remap_blocked") &&
|
|
!row.notes.includes("probe_still_not_implemented")
|
|
) {
|
|
throw new Error(`${row.family}: Python readiness must keep blocked state`);
|
|
}
|
|
}
|
|
|
|
for (const family of requiredFamilies) {
|
|
const familyRows = byFamily.get(family);
|
|
if (!familyRows) {
|
|
throw new Error(`browser_python_native_runtime_readiness: missing ${family}`);
|
|
}
|
|
const requirements = new Set(familyRows.map((row) => row.requirement));
|
|
for (const requirement of [
|
|
"python3",
|
|
"linuxcnc",
|
|
"src/emc/rs274ngc/interp_python.cc",
|
|
"src/emc/pythonplugin/python_plugin.cc",
|
|
]) {
|
|
if (!requirements.has(requirement)) {
|
|
throw new Error(`${family}: missing Python readiness requirement ${requirement}`);
|
|
}
|
|
}
|
|
if (!familyRows.some((row) => row.requirement_kind === "source_config_python_module")) {
|
|
throw new Error(`${family}: Python readiness lacks configured module rows`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserPythonNativeRuntimeProbeGate() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-native-runtime-probe-gate.tsv"),
|
|
);
|
|
if (rows.length !== 1) {
|
|
throw new Error(`browser_python_native_runtime_probe_gate: row count drift ${rows.length}`);
|
|
}
|
|
|
|
const row = rows[0];
|
|
if (
|
|
row.family !== "axis/remap/stop-lookahead/nc_files" ||
|
|
row.blocked !== "L4-PYTHON-REMAP" ||
|
|
row.fixture_id !== "stop_lookahead_python_runtime_lifecycle" ||
|
|
row.required_runtime !== "linuxcnc_python_remap_runtime"
|
|
) {
|
|
throw new Error(`${row.family}: Python runtime probe gate identity drift`);
|
|
}
|
|
if (row.python3_sufficient !== "0") {
|
|
throw new Error(`${row.family}: python3 alone must not satisfy Python remap readiness`);
|
|
}
|
|
for (const field of [
|
|
"python3_available",
|
|
"linuxcnc_available",
|
|
"runtime_owner_sources_ready",
|
|
"representative_modules_ready",
|
|
"runtime_ready",
|
|
"source_proof_ready",
|
|
]) {
|
|
if (!["0", "1"].includes(row[field])) {
|
|
throw new Error(`${row.family}: invalid ${field} ${row[field]}`);
|
|
}
|
|
}
|
|
if (
|
|
row.runtime_owner_sources_ready !== "1" ||
|
|
row.representative_modules_ready !== "1" ||
|
|
row.source_proof_ready !== "1"
|
|
) {
|
|
throw new Error(`${row.family}: Python runtime probe gate source readiness drift`);
|
|
}
|
|
for (const requirement of [
|
|
"python3",
|
|
"linuxcnc",
|
|
"src/emc/rs274ngc/interp_python.cc",
|
|
"src/emc/pythonplugin/python_plugin.cc",
|
|
"axis/remap/stop-lookahead/python/remap.py",
|
|
"axis/remap/stop-lookahead/python/toplevel.py",
|
|
]) {
|
|
if (!row.runtime_requirements.includes(`${requirement}:`)) {
|
|
throw new Error(`${row.family}: Python runtime probe gate missing ${requirement}`);
|
|
}
|
|
}
|
|
for (const modulePath of [
|
|
"axis/remap/stop-lookahead/python/remap.py",
|
|
"axis/remap/stop-lookahead/python/toplevel.py",
|
|
]) {
|
|
if (!row.representative_module_imports.includes(`import:${modulePath}`)) {
|
|
throw new Error(`${row.family}: Python runtime probe gate missing representative import ${modulePath}`);
|
|
}
|
|
}
|
|
if (
|
|
row.required_native_proof !== "linuxcnc_python_runtime_lifecycle_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.family}: Python runtime probe gate must remain pending and disabled`);
|
|
}
|
|
if (!row.notes.includes("python3_alone_not_sufficient")) {
|
|
throw new Error(`${row.family}: Python runtime probe gate must record python3-only guard`);
|
|
}
|
|
if (row.runtime_ready === "1") {
|
|
if (
|
|
row.missing_requirements !== "-" ||
|
|
row.gate_status !== "ready_to_implement_lifecycle_probe"
|
|
) {
|
|
throw new Error(`${row.family}: ready Python runtime probe gate drift`);
|
|
}
|
|
} else if (
|
|
row.missing_requirements === "-" ||
|
|
row.gate_status !== "blocked_missing_host_runtime"
|
|
) {
|
|
throw new Error(`${row.family}: missing-runtime Python probe gate drift`);
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserPythonNativeRuntimeStatePlan() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-native-runtime-state-plan.tsv"),
|
|
);
|
|
const requiredFamilies = new Set([
|
|
"axis/laser",
|
|
"axis/remap/extend-builtins/nc_files",
|
|
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos",
|
|
"gmoccapy/macros",
|
|
]);
|
|
const requiredPhaseTargets = [
|
|
"phase:initialize_python",
|
|
"phase:apply_ini_python_path",
|
|
"phase:execute_toplevel",
|
|
"phase:callable_lookup",
|
|
"phase:pycall_dispatch",
|
|
"phase:callable_invoke",
|
|
"phase:remap_phase_dispatch",
|
|
];
|
|
|
|
if (rows.length !== 12) {
|
|
throw new Error(`browser_python_native_runtime_state_plan: row count drift ${rows.length}`);
|
|
}
|
|
|
|
const byFamily = new Map(rows.map((row) => [row.family, row]));
|
|
for (const family of requiredFamilies) {
|
|
if (!byFamily.has(family)) {
|
|
throw new Error(`browser_python_native_runtime_state_plan: missing ${family}`);
|
|
}
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (row.blocked !== "L4-PYTHON-REMAP") {
|
|
throw new Error(`${row.family}: Python state plan blocked kind drift`);
|
|
}
|
|
if (row.required_runtime !== "linuxcnc_python_remap_runtime") {
|
|
throw new Error(`${row.family}: Python state plan runtime drift`);
|
|
}
|
|
if (row.python_modules === "-" || row.runtime_phases === "-") {
|
|
throw new Error(`${row.family}: Python state plan lacks module or phase evidence`);
|
|
}
|
|
for (const token of [
|
|
"python3",
|
|
"linuxcnc",
|
|
"interp_python.cc",
|
|
"python_plugin.cc",
|
|
"configured_python_modules",
|
|
]) {
|
|
if (!row.required_environment.includes(token)) {
|
|
throw new Error(`${row.family}: Python state plan missing environment ${token}`);
|
|
}
|
|
}
|
|
if (
|
|
!/^[1-9][0-9]*$/.test(row.readiness_requirement_count) ||
|
|
!/^[0-9]+$/.test(row.readiness_available_count)
|
|
) {
|
|
throw new Error(`${row.family}: invalid Python state plan readiness counts`);
|
|
}
|
|
if (
|
|
!row.source_alignment_artifacts.includes("python-remap-native-runtime-alignment.tsv") ||
|
|
!row.source_alignment_artifacts.includes("python-remap-native-runtime-readiness.tsv")
|
|
) {
|
|
throw new Error(`${row.family}: Python state plan missing source artifacts`);
|
|
}
|
|
if (
|
|
row.source_alignment_ok !== "1" ||
|
|
row.native_runtime_status !== "pending_native_python_runtime_probe"
|
|
) {
|
|
throw new Error(`${row.family}: Python state plan source/runtime status drift`);
|
|
}
|
|
for (const target of requiredPhaseTargets) {
|
|
if (!row.required_state_probe.includes(target)) {
|
|
throw new Error(`${row.family}: Python state plan missing target ${target}`);
|
|
}
|
|
}
|
|
if (!row.required_state_probe.includes("module:")) {
|
|
throw new Error(`${row.family}: Python state plan lacks module proof target`);
|
|
}
|
|
if (
|
|
row.proof_kind !== "native_python_runtime_state_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.family}: Python state plan must remain pending and disabled`);
|
|
}
|
|
if (!row.notes.includes("native_runtime_not_executed_keep_python_remap_blocked")) {
|
|
throw new Error(`${row.family}: Python state plan must keep blocked state`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserPythonNativeRuntimeFixturePlan() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/python-remap-native-runtime-fixture-plan.tsv"),
|
|
);
|
|
if (rows.length !== 1) {
|
|
throw new Error(`browser_python_native_runtime_fixture_plan: row count drift ${rows.length}`);
|
|
}
|
|
|
|
const row = rows[0];
|
|
if (row.family !== "axis/remap/stop-lookahead/nc_files") {
|
|
throw new Error(`Python fixture family drift ${row.family}`);
|
|
}
|
|
if (row.blocked !== "L4-PYTHON-REMAP") {
|
|
throw new Error(`${row.family}: Python fixture blocked kind drift`);
|
|
}
|
|
if (row.required_runtime !== "linuxcnc_python_remap_runtime") {
|
|
throw new Error(`${row.family}: Python fixture runtime drift`);
|
|
}
|
|
if (
|
|
row.selection_reason !== "selected_minimal_python_runtime_lifecycle_fixture" ||
|
|
row.fixture_id !== "stop_lookahead_python_runtime_lifecycle" ||
|
|
row.fixture_scope !== "no_python_callables_no_ngc_only_subpaths"
|
|
) {
|
|
throw new Error(`${row.family}: Python fixture selection drift`);
|
|
}
|
|
for (const phase of [
|
|
"initialize_python",
|
|
"apply_ini_python_path",
|
|
"execute_toplevel",
|
|
"callable_lookup",
|
|
"pycall_dispatch",
|
|
"callable_invoke",
|
|
"remap_phase_dispatch",
|
|
]) {
|
|
if (!row.runtime_phases.includes(phase) || !row.expected_runtime_observations.includes(phase)) {
|
|
throw new Error(`${row.family}: Python fixture missing phase ${phase}`);
|
|
}
|
|
}
|
|
for (const modulePath of [
|
|
"axis/remap/stop-lookahead/python/remap.py",
|
|
"axis/remap/stop-lookahead/python/toplevel.py",
|
|
]) {
|
|
if (!row.python_modules.includes(modulePath)) {
|
|
throw new Error(`${row.family}: Python fixture missing module ${modulePath}`);
|
|
}
|
|
if (!row.expected_module_inputs.includes(`module:${modulePath}`)) {
|
|
throw new Error(`${row.family}: Python fixture missing module input ${modulePath}`);
|
|
}
|
|
}
|
|
if (row.process_assumptions !== "hal_process") {
|
|
throw new Error(`${row.family}: Python fixture process assumption drift`);
|
|
}
|
|
if (
|
|
!row.required_environment.includes("python3") ||
|
|
!row.required_environment.includes("linuxcnc") ||
|
|
!row.required_environment.includes("interp_python.cc") ||
|
|
!row.required_environment.includes("python_plugin.cc")
|
|
) {
|
|
throw new Error(`${row.family}: Python fixture environment drift`);
|
|
}
|
|
if (
|
|
row.source_alignment_ok !== "1" ||
|
|
!row.source_alignment_artifacts.includes("python-remap-native-runtime-alignment.tsv") ||
|
|
!row.source_alignment_artifacts.includes("python-remap-native-runtime-readiness.tsv")
|
|
) {
|
|
throw new Error(`${row.family}: Python fixture source alignment drift`);
|
|
}
|
|
if (
|
|
row.proof_kind !== "linuxcnc_python_runtime_lifecycle_probe_required" ||
|
|
row.proof_status !== "pending" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.family}: Python fixture must remain pending and disabled`);
|
|
}
|
|
if (!row.notes.includes("fixture_plan_only_no_python_execution_no_promotion")) {
|
|
throw new Error(`${row.family}: Python fixture must remain non-executing`);
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserNativeProofAlignment() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/native-proof-alignment-summary.tsv"),
|
|
);
|
|
const byClass = new Map(rows.map((row) => [row.boundary_class, row]));
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
proofKey: "millturn_user_m_native_source_state_proof",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
proofKey: "tool_db_native_source_protocol_proof",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
proofKey: "python_remap_native_source_inventory_proof",
|
|
},
|
|
],
|
|
]);
|
|
|
|
for (const [boundaryClass, expectedRow] of expected) {
|
|
const row = byClass.get(boundaryClass);
|
|
if (!row) {
|
|
throw new Error(`browser_native_proof_alignment: missing ${boundaryClass}`);
|
|
}
|
|
if (row.boundary_kind !== expectedRow.boundaryKind) {
|
|
throw new Error(`${boundaryClass}: native proof boundary kind drift`);
|
|
}
|
|
if (row.proof_key !== expectedRow.proofKey) {
|
|
throw new Error(`${boundaryClass}: native proof key drift`);
|
|
}
|
|
if (row.native_summary_available === "0") {
|
|
if (
|
|
row.alignment_ok !== "0" ||
|
|
row.notes !== "native_source_proof_summary_unavailable"
|
|
) {
|
|
throw new Error(`${boundaryClass}: unavailable native proof alignment drift`);
|
|
}
|
|
continue;
|
|
}
|
|
if (
|
|
row.proof_value !== "1" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0" ||
|
|
row.alignment_ok !== "1"
|
|
) {
|
|
throw new Error(`${boundaryClass}: native proof alignment failed`);
|
|
}
|
|
if (row.matching_worklist_count === "0" || row.matching_native_gate_count === "0") {
|
|
throw new Error(`${boundaryClass}: native proof lacks generated gate consumer`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeNativeAlignmentSummary() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-native-alignment-summary.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
artifact: "user-m-process-native-state-alignment.tsv",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
artifact: "tool-db-process-native-protocol-alignment.tsv",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
artifact: "python-remap-native-runtime-alignment.tsv",
|
|
},
|
|
],
|
|
]);
|
|
const byClass = new Map(rows.map((row) => [row.boundary_class, row]));
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_native_alignment_summary: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const [boundaryClass, expectedRow] of expected) {
|
|
const row = byClass.get(boundaryClass);
|
|
if (!row) {
|
|
throw new Error(`browser_runtime_native_alignment_summary: missing ${boundaryClass}`);
|
|
}
|
|
if (row.boundary_kind !== expectedRow.boundaryKind || row.artifact !== expectedRow.artifact) {
|
|
throw new Error(`${boundaryClass}: runtime native alignment summary metadata drift`);
|
|
}
|
|
if (!/^[1-9][0-9]*$/.test(row.row_count)) {
|
|
throw new Error(`${boundaryClass}: invalid runtime alignment row count ${row.row_count}`);
|
|
}
|
|
for (const field of [
|
|
"native_stdout_available_count",
|
|
"alignment_ok_count",
|
|
"pending_count",
|
|
"execution_disabled_count",
|
|
"promotion_disabled_count",
|
|
]) {
|
|
if (row[field] !== row.row_count) {
|
|
throw new Error(`${boundaryClass}: ${field} must match row_count`);
|
|
}
|
|
}
|
|
if (row.summary_ok !== "1") {
|
|
throw new Error(`${boundaryClass}: runtime native alignment summary failed`);
|
|
}
|
|
if (!row.notes.includes("no_runtime_execution")) {
|
|
throw new Error(`${boundaryClass}: runtime native alignment summary must remain non-executing`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserNativeRuntimeProbeSummary() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/native-runtime-probe-summary.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
|
|
requiredNativeProof: "native_runtime_state_probe_required",
|
|
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
target: "axis/db_demo/base.ngc",
|
|
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
|
|
requiredNativeProof: "native_db_process_protocol_probe_required",
|
|
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
|
|
requiredNativeProof: "linuxcnc_python_runtime_lifecycle_probe_required",
|
|
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_native_runtime_probe_summary: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_native_runtime_probe_summary: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.target !== expectedRow.target ||
|
|
row.runtime_probe !== expectedRow.runtimeProbe ||
|
|
row.required_native_proof !== expectedRow.requiredNativeProof ||
|
|
row.opt_in_env !== expectedRow.optInEnv
|
|
) {
|
|
throw new Error(`${row.boundary_class}: native runtime probe metadata drift`);
|
|
}
|
|
if (row.source_proof_ready !== "1") {
|
|
throw new Error(`${row.boundary_class}: native runtime probe source proof is not ready`);
|
|
}
|
|
if (!["0", "1"].includes(row.runtime_ready)) {
|
|
throw new Error(`${row.boundary_class}: invalid runtime readiness`);
|
|
}
|
|
if (
|
|
![
|
|
"skipped_missing_host_runtime",
|
|
"ready_disabled_by_default",
|
|
"not_implemented_full_process_guard",
|
|
"runtime_state_probe_passed",
|
|
"runtime_protocol_probe_passed",
|
|
"runtime_lifecycle_probe_passed",
|
|
].includes(row.probe_status)
|
|
) {
|
|
throw new Error(`${row.boundary_class}: invalid runtime probe status ${row.probe_status}`);
|
|
}
|
|
if (row.runtime_ready === "0" && row.probe_status !== "skipped_missing_host_runtime") {
|
|
throw new Error(`${row.boundary_class}: non-ready runtime must remain skipped`);
|
|
}
|
|
if (row.runtime_ready === "0" && !row.probe_note.includes("missing_host_runtime")) {
|
|
throw new Error(`${row.boundary_class}: missing-runtime probe note drift`);
|
|
}
|
|
if (
|
|
row.probe_status === "ready_disabled_by_default" &&
|
|
!row.probe_note.includes(`set_${row.opt_in_env.split("=")[0]}`)
|
|
) {
|
|
throw new Error(`${row.boundary_class}: ready-disabled probe note drift`);
|
|
}
|
|
if (
|
|
row.probe_status.startsWith("runtime_") &&
|
|
row.probe_status.endsWith("_passed") &&
|
|
!row.probe_note.includes("without_promotion")
|
|
) {
|
|
throw new Error(`${row.boundary_class}: passed probe note drift`);
|
|
}
|
|
if (row.probe_note === "-") {
|
|
throw new Error(`${row.boundary_class}: native runtime probe lacks note`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_class}: native runtime probe must not enable execution or promotion`);
|
|
}
|
|
if (row.stdout_log === "-") {
|
|
throw new Error(`${row.boundary_class}: native runtime probe lacks stdout evidence`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeProbeGateAlignment() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-probe-gate-alignment.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
gateArtifact: "user-m-process-native-runtime-probe-gate.tsv",
|
|
requiredNativeProof: "native_runtime_state_probe_required",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
target: "axis/db_demo/base.ngc",
|
|
gateArtifact: "tool-db-process-native-runtime-probe-gate.tsv",
|
|
requiredNativeProof: "native_db_process_protocol_probe_required",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
gateArtifact: "python-remap-native-runtime-probe-gate.tsv",
|
|
requiredNativeProof: "linuxcnc_python_runtime_lifecycle_probe_required",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_probe_gate_alignment: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_probe_gate_alignment: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.target !== expectedRow.target ||
|
|
row.gate_artifact !== expectedRow.gateArtifact ||
|
|
row.gate_required_native_proof !== expectedRow.requiredNativeProof ||
|
|
row.native_required_native_proof !== expectedRow.requiredNativeProof
|
|
) {
|
|
throw new Error(`${row.boundary_class}: runtime probe gate alignment metadata drift`);
|
|
}
|
|
if (!/^ENABLE_[A-Z0-9_]+=1$/.test(row.native_opt_in_env)) {
|
|
throw new Error(`${row.boundary_class}: runtime probe gate alignment opt-in drift`);
|
|
}
|
|
if (row.gate_runtime_ready !== row.native_runtime_ready) {
|
|
throw new Error(`${row.boundary_class}: gate/native runtime readiness drift`);
|
|
}
|
|
if (row.gate_source_proof_ready !== "1" || row.native_source_proof_ready !== "1") {
|
|
throw new Error(`${row.boundary_class}: runtime probe gate source proof drift`);
|
|
}
|
|
for (const field of [
|
|
"missing_requirements_match",
|
|
"required_native_proof_match",
|
|
"execution_disabled_match",
|
|
"promotion_disabled_match",
|
|
"status_compatible",
|
|
"alignment_ok",
|
|
]) {
|
|
if (row[field] !== "1") {
|
|
throw new Error(`${row.boundary_class}: runtime probe gate ${field} failed`);
|
|
}
|
|
}
|
|
if (!row.notes.includes("before_promotion")) {
|
|
throw new Error(`${row.boundary_class}: runtime probe gate alignment notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function verifyBrowserNativeSourceProofRuntimeProbeParity(
|
|
nativeProofRows,
|
|
nativeRuntimeProbeRows,
|
|
runtimeProbeGateAlignmentRows,
|
|
) {
|
|
const expectedClasses = [
|
|
"L4-USER-M-PROCESS",
|
|
"L4-TOOL-DB",
|
|
"L4-PYTHON-REMAP",
|
|
];
|
|
const proofByClass = new Map(nativeProofRows.map((row) => [row.boundary_class, row]));
|
|
const runtimeByClass = new Map(nativeRuntimeProbeRows.map((row) => [row.boundary_class, row]));
|
|
const gateByClass = new Map(runtimeProbeGateAlignmentRows.map((row) => [row.boundary_class, row]));
|
|
|
|
if (
|
|
nativeProofRows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",") ||
|
|
nativeRuntimeProbeRows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",") ||
|
|
runtimeProbeGateAlignmentRows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",")
|
|
) {
|
|
throw new Error("browser_native_source_proof_runtime_probe_parity: class order drift");
|
|
}
|
|
|
|
for (const boundaryClass of expectedClasses) {
|
|
const proof = proofByClass.get(boundaryClass);
|
|
const runtime = runtimeByClass.get(boundaryClass);
|
|
const gate = gateByClass.get(boundaryClass);
|
|
if (!proof || !runtime || !gate) {
|
|
throw new Error(`${boundaryClass}: missing native proof/runtime/gate parity row`);
|
|
}
|
|
if (runtime.boundary_kind !== proof.boundary_kind || gate.boundary_kind !== runtime.boundary_kind) {
|
|
throw new Error(`${boundaryClass}: native proof/runtime/gate boundary kind drift`);
|
|
}
|
|
if (proof.target !== "*" && runtime.target !== proof.target) {
|
|
throw new Error(`${boundaryClass}: native proof/runtime target drift`);
|
|
}
|
|
if (gate.target !== runtime.target) {
|
|
throw new Error(`${boundaryClass}: runtime gate target drift`);
|
|
}
|
|
if (
|
|
proof.alignment_ok !== "1" ||
|
|
runtime.source_proof_ready !== proof.alignment_ok ||
|
|
gate.native_source_proof_ready !== proof.alignment_ok ||
|
|
gate.gate_source_proof_ready !== proof.alignment_ok
|
|
) {
|
|
throw new Error(`${boundaryClass}: native source proof readiness drift`);
|
|
}
|
|
if (
|
|
runtime.required_native_proof !== gate.native_required_native_proof ||
|
|
gate.required_native_proof_match !== "1"
|
|
) {
|
|
throw new Error(`${boundaryClass}: native runtime proof requirement drift`);
|
|
}
|
|
if (
|
|
runtime.runtime_ready !== gate.native_runtime_ready ||
|
|
gate.gate_runtime_ready !== gate.native_runtime_ready ||
|
|
gate.missing_requirements_match !== "1" ||
|
|
gate.status_compatible !== "1"
|
|
) {
|
|
throw new Error(`${boundaryClass}: native runtime readiness/status drift`);
|
|
}
|
|
if (
|
|
proof.execution_enabled !== "0" ||
|
|
proof.promotion_allowed !== "0" ||
|
|
runtime.execution_enabled !== "0" ||
|
|
runtime.promotion_allowed !== "0" ||
|
|
gate.execution_disabled_match !== "1" ||
|
|
gate.promotion_disabled_match !== "1" ||
|
|
gate.alignment_ok !== "1"
|
|
) {
|
|
throw new Error(`${boundaryClass}: native proof/runtime/gate parity must remain disabled`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserNativeRuntimeProbeExecutionPlan() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/native-runtime-probe-execution-plan.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
|
|
requiredNativeProof: "native_runtime_state_probe_required",
|
|
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
|
|
script: "wasm-port/tests/native/probe_millturn_user_m_runtime.sh",
|
|
expectedPassStatus: "runtime_state_probe_passed",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
target: "axis/db_demo/base.ngc",
|
|
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
|
|
requiredNativeProof: "native_db_process_protocol_probe_required",
|
|
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
|
|
script: "wasm-port/tests/native/probe_tool_db_runtime.sh",
|
|
expectedPassStatus: "runtime_protocol_probe_passed",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
|
|
requiredNativeProof: "linuxcnc_python_runtime_lifecycle_probe_required",
|
|
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
|
|
script: "wasm-port/tests/native/probe_python_remap_runtime.sh",
|
|
expectedPassStatus: "runtime_lifecycle_probe_passed",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_native_runtime_probe_execution_plan: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_native_runtime_probe_execution_plan: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.target !== expectedRow.target ||
|
|
row.runtime_probe !== expectedRow.runtimeProbe ||
|
|
row.required_native_proof !== expectedRow.requiredNativeProof ||
|
|
row.opt_in_env !== expectedRow.optInEnv ||
|
|
row.expected_pass_status !== expectedRow.expectedPassStatus
|
|
) {
|
|
throw new Error(`${row.boundary_class}: native runtime execution plan metadata drift`);
|
|
}
|
|
if (row.execution_command !== `${expectedRow.optInEnv} bash ${expectedRow.script}`) {
|
|
throw new Error(`${row.boundary_class}: native runtime execution command drift`);
|
|
}
|
|
if (row.source_proof_ready !== "1" || row.gate_alignment_ok !== "1") {
|
|
throw new Error(`${row.boundary_class}: native runtime execution plan is not source/alignment ready`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_class}: native runtime execution plan must not enable execution or promotion`);
|
|
}
|
|
for (const requirement of [
|
|
`native:${expectedRow.expectedPassStatus}`,
|
|
"node:inventory_gate_alignment_after_native_pass",
|
|
"browser:smoke_gate_alignment_after_node_pass",
|
|
"manual:promotion_lock_update_required",
|
|
]) {
|
|
if (!row.promotion_requires.includes(requirement)) {
|
|
throw new Error(`${row.boundary_class}: native runtime execution plan missing ${requirement}`);
|
|
}
|
|
}
|
|
if (row.current_probe_status === row.expected_pass_status) {
|
|
if (row.plan_status !== "native_probe_passed_waiting_for_node_browser_promotion_proof") {
|
|
throw new Error(`${row.boundary_class}: passed execution plan status drift`);
|
|
}
|
|
} else if (row.runtime_ready === "1") {
|
|
if (row.plan_status !== "ready_to_run_opt_in_probe" || row.missing_requirements !== "-") {
|
|
throw new Error(`${row.boundary_class}: ready execution plan status drift`);
|
|
}
|
|
} else if (
|
|
row.plan_status !== "blocked_missing_host_runtime" ||
|
|
row.missing_requirements === "-"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: blocked execution plan status drift`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: native runtime execution plan notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserNativeRuntimeProbePassEvidenceContract() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/native-runtime-probe-pass-evidence-contract.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
|
|
expectedPassStatus: "runtime_state_probe_passed",
|
|
evidence: [
|
|
"millturn_user_m_runtime_probe_status=runtime_state_probe_passed",
|
|
"millturn_user_m_M128_runtime_state_ok=1",
|
|
"millturn_user_m_M129_runtime_state_ok=1",
|
|
],
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
target: "axis/db_demo/base.ngc",
|
|
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
|
|
expectedPassStatus: "runtime_protocol_probe_passed",
|
|
evidence: [
|
|
"tool_db_runtime_probe_status=runtime_protocol_probe_passed",
|
|
"tool_db_protocol_version=v2.1",
|
|
"tool_db_runtime_protocol_probe_ok=1",
|
|
"tool_db_persistence_state_ok=1",
|
|
],
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
|
|
expectedPassStatus: "runtime_lifecycle_probe_passed",
|
|
evidence: [
|
|
"python_remap_runtime_probe_status=runtime_lifecycle_probe_passed",
|
|
"python_remap_lifecycle_generator_first_yield=2",
|
|
"python_remap_runtime_lifecycle_probe_ok=1",
|
|
],
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_native_runtime_probe_pass_evidence_contract: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_native_runtime_probe_pass_evidence_contract: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.target !== expectedRow.target ||
|
|
row.runtime_probe !== expectedRow.runtimeProbe ||
|
|
row.expected_pass_status !== expectedRow.expectedPassStatus ||
|
|
row.stdout_log === "-"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: native pass evidence metadata drift`);
|
|
}
|
|
for (const evidence of expectedRow.evidence) {
|
|
if (!row.expected_evidence_keys.includes(evidence)) {
|
|
throw new Error(`${row.boundary_class}: native pass evidence missing ${evidence}`);
|
|
}
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_class}: native pass evidence contract must not enable execution or promotion`);
|
|
}
|
|
if (row.current_probe_status === row.expected_pass_status) {
|
|
if (
|
|
row.evidence_required_now !== "1" ||
|
|
row.observed_evidence_ready !== "1" ||
|
|
row.missing_evidence_keys !== "-" ||
|
|
row.evidence_status !== "native_pass_evidence_observed"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: native pass evidence must be observed after pass`);
|
|
}
|
|
} else if (
|
|
row.evidence_required_now !== "0" ||
|
|
row.observed_evidence_ready !== "0" ||
|
|
row.missing_evidence_keys !== "-" ||
|
|
row.evidence_status !== "pending_until_native_pass"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: native pass evidence must remain pending before pass`);
|
|
}
|
|
if (
|
|
!row.notes.includes("no_automatic_promotion") &&
|
|
!row.notes.includes("before_node_browser_promotion")
|
|
) {
|
|
throw new Error(`${row.boundary_class}: native pass evidence notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryContractSummary() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-contract-summary.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
contractArtifact: "user-m-process-transition-plan.tsv",
|
|
rowCount: "2",
|
|
runtimeAlignmentArtifact: "user-m-process-native-state-alignment.tsv",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
contractArtifact: "tool-db-process-transaction-plan.tsv",
|
|
rowCount: "5",
|
|
runtimeAlignmentArtifact: "tool-db-process-native-protocol-alignment.tsv",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
contractArtifact: "python-remap-runtime-contract.tsv",
|
|
rowCount: "12",
|
|
runtimeAlignmentArtifact: "python-remap-native-runtime-alignment.tsv",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_contract_summary: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_contract_summary: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.contract_artifact !== expectedRow.contractArtifact ||
|
|
row.contract_row_count !== expectedRow.rowCount ||
|
|
row.runtime_alignment_artifact !== expectedRow.runtimeAlignmentArtifact
|
|
) {
|
|
throw new Error(`${row.boundary_class}: runtime contract summary metadata drift`);
|
|
}
|
|
for (const field of [
|
|
"pending_count",
|
|
"execution_disabled_count",
|
|
"promotion_disabled_count",
|
|
]) {
|
|
if (row[field] !== row.contract_row_count) {
|
|
throw new Error(`${row.boundary_class}: runtime contract ${field} drift`);
|
|
}
|
|
}
|
|
if (row.runtime_alignment_ok !== "1" || row.contract_ok !== "1") {
|
|
throw new Error(`${row.boundary_class}: runtime contract summary failed`);
|
|
}
|
|
if (!row.notes.includes("no_runtime_execution") || !row.notes.includes("no_promotion")) {
|
|
throw new Error(`${row.boundary_class}: runtime contract summary must remain non-executing`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function verifyBrowserRuntimeContractNativeAlignmentParity(contractRows, nativeAlignmentRows) {
|
|
const nativeByClass = new Map(nativeAlignmentRows.map((row) => [row.boundary_class, row]));
|
|
if (contractRows.length !== nativeAlignmentRows.length) {
|
|
throw new Error("browser_runtime_contract_native_alignment_parity: row count drift");
|
|
}
|
|
|
|
for (const contract of contractRows) {
|
|
const native = nativeByClass.get(contract.boundary_class);
|
|
if (!native) {
|
|
throw new Error(`${contract.boundary_class}: missing native alignment summary row`);
|
|
}
|
|
if (
|
|
contract.boundary_kind !== native.boundary_kind ||
|
|
contract.runtime_alignment_artifact !== native.artifact ||
|
|
contract.runtime_alignment_row_count !== native.row_count ||
|
|
contract.runtime_alignment_ok !== native.summary_ok
|
|
) {
|
|
throw new Error(`${contract.boundary_class}: contract/native alignment summary drift`);
|
|
}
|
|
if (contract.contract_ok !== "1" || native.summary_ok !== "1") {
|
|
throw new Error(`${contract.boundary_class}: contract/native alignment summary must remain OK`);
|
|
}
|
|
if (
|
|
contract.execution_disabled_count !== contract.contract_row_count ||
|
|
contract.promotion_disabled_count !== contract.contract_row_count ||
|
|
native.execution_disabled_count !== native.row_count ||
|
|
native.promotion_disabled_count !== native.row_count
|
|
) {
|
|
throw new Error(`${contract.boundary_class}: contract/native alignment must remain disabled`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyBrowserRuntimeFamilyContractAlignmentParity({
|
|
userMTransitionRows,
|
|
userMRuntimeStatePlanRows,
|
|
userMRuntimeReadinessRows,
|
|
userMRuntimeProbeGateRows,
|
|
toolDbTransactionRows,
|
|
toolDbRuntimeReadinessRows,
|
|
toolDbRuntimeProbeGateRows,
|
|
pythonRuntimeContractRows,
|
|
pythonRuntimeReadinessRows,
|
|
pythonRuntimeProbeGateRows,
|
|
pythonRuntimeStatePlanRows,
|
|
pythonRuntimeFixturePlanRows,
|
|
runtimeContractSummaryRows,
|
|
}) {
|
|
const unavailableRequirements = (rows) =>
|
|
rows.filter((row) => row.available === "0").map((row) => row.requirement).join(",");
|
|
const contractByClass = new Map(runtimeContractSummaryRows.map((row) => [row.boundary_class, row]));
|
|
const requireDisabled = (row, label) => {
|
|
if (row.proof_status !== "pending" || row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${label}: family contract alignment must remain pending and disabled`);
|
|
}
|
|
};
|
|
|
|
const userMContract = contractByClass.get("L4-USER-M-PROCESS");
|
|
if (
|
|
!userMContract ||
|
|
userMContract.contract_artifact !== "user-m-process-transition-plan.tsv" ||
|
|
userMContract.contract_row_count !== String(userMTransitionRows.length) ||
|
|
userMContract.contract_ok !== "1"
|
|
) {
|
|
throw new Error("browser_runtime_family_contract_alignment: user-M contract summary drift");
|
|
}
|
|
if (
|
|
userMRuntimeStatePlanRows.length !== userMTransitionRows.length ||
|
|
userMRuntimeProbeGateRows.length !== userMTransitionRows.length
|
|
) {
|
|
throw new Error("browser_runtime_family_contract_alignment: user-M row count drift");
|
|
}
|
|
const userMTransitionByRemap = new Map(userMTransitionRows.map((row) => [row.remap_code, row]));
|
|
const userMProbeByRemap = new Map(userMRuntimeProbeGateRows.map((row) => [row.remap_code, row]));
|
|
const userMMissingRequirements = unavailableRequirements(userMRuntimeReadinessRows);
|
|
for (const plan of userMRuntimeStatePlanRows) {
|
|
const transition = userMTransitionByRemap.get(plan.remap_code);
|
|
const probe = userMProbeByRemap.get(plan.remap_code);
|
|
if (!transition || !probe) {
|
|
throw new Error(`${plan.remap_code}: user-M family alignment missing transition/probe row`);
|
|
}
|
|
for (const field of [
|
|
"path",
|
|
"ini",
|
|
"blocked",
|
|
"user_m_code",
|
|
"switchkins_target",
|
|
"active_g5x",
|
|
"work_offset_pocket",
|
|
"expected_state_pins",
|
|
]) {
|
|
if (plan[field] !== transition[field]) {
|
|
throw new Error(`${plan.remap_code}: user-M family alignment ${field} drift`);
|
|
}
|
|
}
|
|
if (
|
|
probe.required_runtime !== plan.required_runtime ||
|
|
probe.required_native_proof !== plan.proof_kind ||
|
|
probe.source_proof_ready !== "1" ||
|
|
probe.missing_requirements !== userMMissingRequirements
|
|
) {
|
|
throw new Error(`${plan.remap_code}: user-M family probe/readiness drift`);
|
|
}
|
|
requireDisabled(transition, `${plan.remap_code}: user-M transition`);
|
|
requireDisabled(plan, `${plan.remap_code}: user-M runtime plan`);
|
|
requireDisabled(probe, `${plan.remap_code}: user-M probe gate`);
|
|
}
|
|
|
|
const toolDbContract = contractByClass.get("L4-TOOL-DB");
|
|
if (
|
|
!toolDbContract ||
|
|
toolDbContract.contract_artifact !== "tool-db-process-transaction-plan.tsv" ||
|
|
toolDbContract.contract_row_count !== String(toolDbTransactionRows.length) ||
|
|
toolDbContract.contract_ok !== "1" ||
|
|
toolDbRuntimeProbeGateRows.length !== 1
|
|
) {
|
|
throw new Error("browser_runtime_family_contract_alignment: tool DB contract summary drift");
|
|
}
|
|
const toolDbProbe = toolDbRuntimeProbeGateRows[0];
|
|
if (toolDbProbe.missing_requirements !== unavailableRequirements(toolDbRuntimeReadinessRows)) {
|
|
throw new Error("browser_runtime_family_contract_alignment: tool DB missing requirements drift");
|
|
}
|
|
for (const transaction of toolDbTransactionRows) {
|
|
if (
|
|
transaction.db_program !== toolDbProbe.db_program ||
|
|
!toolDbProbe.protocol_transactions.includes(`${transaction.transaction_phase}:${transaction.protocol_message}`)
|
|
) {
|
|
throw new Error(`${transaction.transaction_phase}: tool DB family transaction/probe drift`);
|
|
}
|
|
requireDisabled(transaction, `${transaction.transaction_phase}: tool DB transaction`);
|
|
}
|
|
if (
|
|
toolDbProbe.required_native_proof !== "native_db_process_protocol_probe_required" ||
|
|
toolDbProbe.source_proof_ready !== "1"
|
|
) {
|
|
throw new Error("browser_runtime_family_contract_alignment: tool DB proof drift");
|
|
}
|
|
requireDisabled(toolDbProbe, "tool DB probe gate");
|
|
|
|
const pythonContract = contractByClass.get("L4-PYTHON-REMAP");
|
|
if (
|
|
!pythonContract ||
|
|
pythonContract.contract_artifact !== "python-remap-runtime-contract.tsv" ||
|
|
pythonContract.contract_row_count !== String(pythonRuntimeContractRows.length) ||
|
|
pythonContract.contract_ok !== "1" ||
|
|
pythonRuntimeStatePlanRows.length !== pythonRuntimeContractRows.length ||
|
|
pythonRuntimeProbeGateRows.length !== 1 ||
|
|
pythonRuntimeFixturePlanRows.length !== 1
|
|
) {
|
|
throw new Error("browser_runtime_family_contract_alignment: Python contract summary drift");
|
|
}
|
|
const pythonContractByFamily = new Map(pythonRuntimeContractRows.map((row) => [row.family, row]));
|
|
const pythonStateByFamily = new Map(pythonRuntimeStatePlanRows.map((row) => [row.family, row]));
|
|
for (const contract of pythonRuntimeContractRows) {
|
|
const statePlan = pythonStateByFamily.get(contract.family);
|
|
if (!statePlan) {
|
|
throw new Error(`${contract.family}: Python family state plan missing`);
|
|
}
|
|
for (const field of [
|
|
"blocked",
|
|
"runtime_phases",
|
|
"python_modules",
|
|
"python_callables",
|
|
"ngc_only_subpaths",
|
|
"process_assumptions",
|
|
]) {
|
|
if (statePlan[field] !== contract[field]) {
|
|
throw new Error(`${contract.family}: Python family ${field} drift`);
|
|
}
|
|
}
|
|
requireDisabled(contract, `${contract.family}: Python contract`);
|
|
requireDisabled(statePlan, `${contract.family}: Python state plan`);
|
|
}
|
|
const pythonProbe = pythonRuntimeProbeGateRows[0];
|
|
const pythonFixture = pythonRuntimeFixturePlanRows[0];
|
|
const pythonFixtureContract = pythonContractByFamily.get(pythonFixture.family);
|
|
const pythonFixtureState = pythonStateByFamily.get(pythonFixture.family);
|
|
if (!pythonFixtureContract || !pythonFixtureState || pythonProbe.family !== pythonFixture.family) {
|
|
throw new Error("browser_runtime_family_contract_alignment: Python fixture/probe family drift");
|
|
}
|
|
if (
|
|
pythonFixture.runtime_phases !== pythonFixtureContract.runtime_phases ||
|
|
pythonFixture.python_modules !== pythonFixtureContract.python_modules ||
|
|
pythonFixture.process_assumptions !== pythonFixtureContract.process_assumptions ||
|
|
pythonProbe.required_native_proof !== pythonFixture.proof_kind ||
|
|
pythonProbe.source_proof_ready !== "1"
|
|
) {
|
|
throw new Error("browser_runtime_family_contract_alignment: Python fixture/probe proof drift");
|
|
}
|
|
const pythonFixtureMissingRequirements = pythonRuntimeReadinessRows
|
|
.filter((row) => row.family === pythonProbe.family && row.available === "0")
|
|
.map((row) => row.requirement)
|
|
.join(",");
|
|
if (pythonProbe.missing_requirements !== pythonFixtureMissingRequirements) {
|
|
throw new Error("browser_runtime_family_contract_alignment: Python missing requirements drift");
|
|
}
|
|
requireDisabled(pythonFixture, "Python fixture plan");
|
|
requireDisabled(pythonProbe, "Python probe gate");
|
|
|
|
for (const summary of runtimeContractSummaryRows) {
|
|
if (
|
|
summary.pending_count !== summary.contract_row_count ||
|
|
summary.execution_disabled_count !== summary.contract_row_count ||
|
|
summary.promotion_disabled_count !== summary.contract_row_count ||
|
|
summary.runtime_alignment_ok !== "1" ||
|
|
summary.contract_ok !== "1"
|
|
) {
|
|
throw new Error(`${summary.boundary_class}: runtime family contract summary status drift`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserRuntimeNativeAlignmentDetails(summaryRows) {
|
|
for (const summary of summaryRows) {
|
|
const rows = parseTsv(
|
|
await fetchText(`../../build/wasm/sim-configs-inventory/${summary.artifact}`),
|
|
);
|
|
if (String(rows.length) !== summary.row_count) {
|
|
throw new Error(`${summary.boundary_class}: detailed alignment row count drift`);
|
|
}
|
|
for (const row of rows) {
|
|
if (row.blocked !== summary.boundary_class) {
|
|
throw new Error(`${summary.boundary_class}: detailed alignment blocked kind drift`);
|
|
}
|
|
if (row.native_stdout_available !== "1" || row.alignment_ok !== "1") {
|
|
throw new Error(`${summary.boundary_class}: detailed alignment failed`);
|
|
}
|
|
if (row.proof_status !== "pending") {
|
|
throw new Error(`${summary.boundary_class}: detailed alignment proof must remain pending`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${summary.boundary_class}: detailed alignment must not enable execution or promotion`);
|
|
}
|
|
if (!row.notes.includes("no_")) {
|
|
throw new Error(`${summary.boundary_class}: detailed alignment must record non-execution notes`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserBlockedRuntimePromotionLock() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/blocked-runtime-promotion-lock.tsv"),
|
|
);
|
|
const requiredTargets = new Map([
|
|
["axis/vismach/millturn/example.ngc", "L4-USER-M-PROCESS"],
|
|
["axis/db_demo/base.ngc", "L4-TOOL-DB"],
|
|
]);
|
|
|
|
if (rows.length < requiredTargets.size) {
|
|
throw new Error("browser_blocked_runtime_promotion_lock: too few rows");
|
|
}
|
|
|
|
const byTarget = new Map(rows.map((row) => [row.target, row]));
|
|
for (const [target, blocked] of requiredTargets) {
|
|
const row = byTarget.get(target);
|
|
if (!row) {
|
|
throw new Error(`browser_blocked_runtime_promotion_lock: missing ${target}`);
|
|
}
|
|
if (row.blocked !== blocked) {
|
|
throw new Error(`${target}: promotion lock blocked kind drift`);
|
|
}
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.target}: promotion lock must keep execution and promotion disabled`);
|
|
}
|
|
for (const field of [
|
|
"native_gate_pending",
|
|
"node_gate_pending",
|
|
"browser_gate_pending",
|
|
"runtime_alignment_ok",
|
|
"lock_active",
|
|
]) {
|
|
if (row[field] !== "1") {
|
|
throw new Error(`${row.target}: promotion lock ${field} drift`);
|
|
}
|
|
}
|
|
if (!/^[1-9][0-9]*$/.test(row.runtime_alignment_row_count)) {
|
|
throw new Error(`${row.target}: invalid promotion lock alignment count`);
|
|
}
|
|
if (!row.notes.includes("blocked_until_linuxcnc_owned_runtime_boundary")) {
|
|
throw new Error(`${row.target}: promotion lock note drift`);
|
|
}
|
|
if (row.boundary_kind === "python_runtime" && row.design_status !== "inventory_only") {
|
|
throw new Error(`${row.target}: Python promotion lock must remain inventory-only`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryPromotionReadiness() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-promotion-readiness.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
blocked: "L4-USER-M-PROCESS",
|
|
expectedPassStatus: "runtime_state_probe_passed",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
target: "axis/db_demo/base.ngc",
|
|
blocked: "L4-TOOL-DB",
|
|
expectedPassStatus: "runtime_protocol_probe_passed",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
blocked: "L4-PYTHON-REMAP",
|
|
expectedPassStatus: "runtime_lifecycle_probe_passed",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_promotion_readiness: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_promotion_readiness: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.target !== expectedRow.target ||
|
|
row.blocked !== expectedRow.blocked ||
|
|
row.expected_pass_status !== expectedRow.expectedPassStatus ||
|
|
row.native_evidence_status === "-"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: promotion readiness metadata drift`);
|
|
}
|
|
if (
|
|
row.node_inventory_gate_complete !== "0" ||
|
|
row.browser_smoke_gate_complete !== "0" ||
|
|
row.promotion_lock_active !== "1" ||
|
|
row.manual_lock_update_required !== "1" ||
|
|
row.promotion_ready !== "0" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: promotion readiness must remain locked`);
|
|
}
|
|
if (row.current_probe_status === row.expected_pass_status) {
|
|
if (row.native_pass_ready !== "1" || row.blocking_reason === "awaiting_native_runtime_probe_pass") {
|
|
throw new Error(`${row.boundary_class}: passed promotion readiness state drift`);
|
|
}
|
|
} else if (
|
|
row.native_pass_ready !== "0" ||
|
|
row.blocking_reason !== "awaiting_native_runtime_probe_pass"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: pending promotion readiness state drift`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: promotion readiness notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryPromotionBlockers() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
blocked: "L4-USER-M-PROCESS",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
target: "axis/db_demo/base.ngc",
|
|
blocked: "L4-TOOL-DB",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
blocked: "L4-PYTHON-REMAP",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_promotion_blockers: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_promotion_blockers: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.target !== expectedRow.target ||
|
|
row.blocked !== expectedRow.blocked
|
|
) {
|
|
throw new Error(`${row.boundary_class}: promotion blocker metadata drift`);
|
|
}
|
|
if (
|
|
row.promotion_ready !== "0" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0" ||
|
|
row.blocker_keys === "-" ||
|
|
row.next_unblock_action === "-"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: promotion blocker summary must remain blocked and actionable`);
|
|
}
|
|
if (!/^[1-9][0-9]*$/.test(row.blocker_count)) {
|
|
throw new Error(`${row.boundary_class}: invalid promotion blocker count`);
|
|
}
|
|
for (const blocker of [
|
|
"node_inventory_gate_not_complete",
|
|
"browser_smoke_gate_not_complete",
|
|
"promotion_lock_active",
|
|
"manual_lock_update_required",
|
|
]) {
|
|
if (!row.blocker_keys.includes(blocker)) {
|
|
throw new Error(`${row.boundary_class}: promotion blocker missing ${blocker}`);
|
|
}
|
|
}
|
|
if (
|
|
row.missing_runtime_requirements !== "-" &&
|
|
!row.blocker_keys.includes("host_runtime_requirements_missing")
|
|
) {
|
|
throw new Error(`${row.boundary_class}: host runtime blocker missing`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: promotion blocker notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryPostNativePassGates() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-post-native-pass-gates.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
blocked: "L4-USER-M-PROCESS",
|
|
nodeProof: "not_user_m_event_only",
|
|
browserProof: "no_full_process_claim",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
target: "axis/db_demo/base.ngc",
|
|
blocked: "L4-TOOL-DB",
|
|
nodeProof: "not_tbl_fallback",
|
|
browserProof: "opfs_persistence_only",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
blocked: "L4-PYTHON-REMAP",
|
|
nodeProof: "not_js_semantics",
|
|
browserProof: "no_full_process_claim",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_post_native_pass_gates: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_post_native_pass_gates: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.target !== expectedRow.target ||
|
|
row.blocked !== expectedRow.blocked
|
|
) {
|
|
throw new Error(`${row.boundary_class}: post-native-pass gate metadata drift`);
|
|
}
|
|
if (
|
|
!row.required_node_proof.includes(expectedRow.nodeProof) ||
|
|
!row.required_browser_proof.includes(expectedRow.browserProof)
|
|
) {
|
|
throw new Error(`${row.boundary_class}: post-native-pass proof requirement drift`);
|
|
}
|
|
if (
|
|
row.manual_lock_update_required !== "1" ||
|
|
row.promotion_lock_active !== "1" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: post-native-pass gate must remain locked`);
|
|
}
|
|
if (row.native_evidence_ready === "1") {
|
|
if (
|
|
row.native_pass_ready !== "1" ||
|
|
row.node_gate_status !== "pending_node_inventory_promotion_gate" ||
|
|
row.browser_gate_status !== "blocked_until_node_gate_complete" ||
|
|
row.gate_status !== "waiting_for_node_browser_manual_promotion"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: post-native-pass ready state drift`);
|
|
}
|
|
} else if (
|
|
row.node_gate_status !== "blocked_until_native_pass_evidence" ||
|
|
row.browser_gate_status !== "blocked_until_node_gate_complete" ||
|
|
row.gate_status !== "blocked_before_native_pass"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: post-native-pass blocked state drift`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: post-native-pass notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryHostPreflight() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-host-preflight.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
boundaryKind: "external_user_m_process",
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
blocked: "L4-USER-M-PROCESS",
|
|
requiredRuntime: "linuxcnc_task_hal_tcl_user_m_process",
|
|
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
|
|
script: "probe_millturn_user_m_runtime.sh",
|
|
missingToken: "linuxcnc",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
boundaryKind: "tool_database_process",
|
|
target: "axis/db_demo/base.ngc",
|
|
blocked: "L4-TOOL-DB",
|
|
requiredRuntime: "linuxcnc_tooldata_db_process",
|
|
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
|
|
script: "probe_tool_db_runtime.sh",
|
|
missingToken: "milltask",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
boundaryKind: "python_runtime",
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
blocked: "L4-PYTHON-REMAP",
|
|
requiredRuntime: "linuxcnc_python_remap_runtime",
|
|
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
|
|
script: "probe_python_remap_runtime.sh",
|
|
missingToken: "linuxcnc",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_host_preflight: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_host_preflight: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.boundary_kind !== expectedRow.boundaryKind ||
|
|
row.target !== expectedRow.target ||
|
|
row.blocked !== expectedRow.blocked ||
|
|
row.required_runtime !== expectedRow.requiredRuntime ||
|
|
row.opt_in_env !== expectedRow.optInEnv
|
|
) {
|
|
throw new Error(`${row.boundary_class}: host preflight metadata drift`);
|
|
}
|
|
if (
|
|
row.source_proof_ready !== "1" ||
|
|
row.required_native_proof === "-" ||
|
|
row.runtime_requirements === "-" ||
|
|
!row.runtime_requirements.includes(`${expectedRow.missingToken}:`)
|
|
) {
|
|
throw new Error(`${row.boundary_class}: host preflight readiness evidence drift`);
|
|
}
|
|
if (row.execution_command !== `${row.opt_in_env} bash wasm-port/tests/native/${expectedRow.script}`) {
|
|
throw new Error(`${row.boundary_class}: host preflight execution command drift`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_class}: host preflight must not enable execution or promotion`);
|
|
}
|
|
if (row.runtime_ready === "1") {
|
|
if (row.missing_requirements !== "-" || row.preflight_status !== "ready_to_run_opt_in_native_probe") {
|
|
throw new Error(`${row.boundary_class}: ready host preflight drift`);
|
|
}
|
|
} else if (
|
|
!row.missing_requirements.includes(expectedRow.missingToken) ||
|
|
row.preflight_status !== "blocked_missing_host_runtime"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: blocked host preflight drift`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: host preflight notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryHostRequirementSummary() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-host-requirement-summary.tsv"),
|
|
);
|
|
const byRequirement = new Map(rows.map((row) => [row.requirement, row]));
|
|
const required = new Map([
|
|
["linuxcnc", { available: "0", requiredByCount: "3", missingFor: "L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS" }],
|
|
["halcmd", { available: "0", requiredByCount: "2", missingFor: "L4-TOOL-DB,L4-USER-M-PROCESS" }],
|
|
["milltask", { available: "0", requiredByCount: "1", missingFor: "L4-TOOL-DB" }],
|
|
["python3", { available: "1", requiredByCount: "2", missingFor: "-" }],
|
|
["tclsh", { available: "1", requiredByCount: "1", missingFor: "-" }],
|
|
]);
|
|
|
|
if (rows.length < 8) {
|
|
throw new Error(`browser_runtime_boundary_host_requirement_summary: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const [requirement, expected] of required) {
|
|
const row = byRequirement.get(requirement);
|
|
if (!row) {
|
|
throw new Error(`browser_runtime_boundary_host_requirement_summary: missing ${requirement}`);
|
|
}
|
|
if (
|
|
row.available !== expected.available ||
|
|
row.required_by_count !== expected.requiredByCount ||
|
|
row.missing_for !== expected.missingFor
|
|
) {
|
|
throw new Error(`${requirement}: host requirement summary drift`);
|
|
}
|
|
}
|
|
|
|
for (const row of rows) {
|
|
if (!["host_command", "source_or_module", "runtime_input"].includes(row.requirement_kind)) {
|
|
throw new Error(`${row.requirement}: invalid host requirement kind`);
|
|
}
|
|
if (!["0", "1"].includes(row.available)) {
|
|
throw new Error(`${row.requirement}: invalid host requirement availability`);
|
|
}
|
|
if (!/^[1-9][0-9]*$/.test(row.required_by_count)) {
|
|
throw new Error(`${row.requirement}: invalid host requirement count`);
|
|
}
|
|
if (row.required_by === "-" || row.blocked_families === "-" || row.opt_in_envs === "-") {
|
|
throw new Error(`${row.requirement}: host requirement summary lacks traceability`);
|
|
}
|
|
if (row.available === "0") {
|
|
if (row.missing_for === "-" || row.missing_blocks_runtime !== "1") {
|
|
throw new Error(`${row.requirement}: unavailable host requirement must block runtime`);
|
|
}
|
|
} else if (row.missing_for !== "-" || row.missing_blocks_runtime !== "0") {
|
|
throw new Error(`${row.requirement}: available host requirement must not block runtime`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.requirement}: host requirement summary must not enable execution or promotion`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.requirement}: host requirement summary notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryHostUnblockPlan() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-host-unblock-plan.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
["linuxcnc", { missingFor: "L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS", remaining: "halcmd,halrun,milltask" }],
|
|
["halcmd", { missingFor: "L4-TOOL-DB,L4-USER-M-PROCESS", remaining: "halrun,linuxcnc,milltask" }],
|
|
["halrun", { missingFor: "L4-USER-M-PROCESS", remaining: "halcmd,linuxcnc" }],
|
|
["milltask", { missingFor: "L4-TOOL-DB", remaining: "halcmd,linuxcnc" }],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_host_unblock_plan: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.requirement);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_host_unblock_plan: unexpected ${row.requirement}`);
|
|
}
|
|
if (
|
|
row.requirement_kind !== "host_command" ||
|
|
row.available !== "0" ||
|
|
row.missing_for !== expectedRow.missingFor ||
|
|
row.remaining_missing_after_requirement !== expectedRow.remaining
|
|
) {
|
|
throw new Error(`${row.requirement}: host unblock plan metadata drift`);
|
|
}
|
|
if (
|
|
row.affected_targets === "-" ||
|
|
row.opt_in_envs === "-" ||
|
|
row.affected_execution_commands === "-"
|
|
) {
|
|
throw new Error(`${row.requirement}: host unblock plan lacks traceability`);
|
|
}
|
|
if (
|
|
row.unblocks_when_available !== "partial_unblock_other_requirements_remain" ||
|
|
row.next_action !== "provide_missing_host_requirement"
|
|
) {
|
|
throw new Error(`${row.requirement}: host unblock plan status drift`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.requirement}: host unblock plan must not enable execution or promotion`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.requirement}: host unblock plan notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryFamilyHostReadiness() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-family-host-readiness.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
missingCount: "3",
|
|
missing: "halrun,halcmd,linuxcnc",
|
|
available: "tclsh",
|
|
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
target: "axis/db_demo/base.ngc",
|
|
missingCount: "3",
|
|
missing: "linuxcnc,milltask,halcmd",
|
|
available: "python3,axis/db_demo/db_nonran.py,linuxcnc.so,tooldb.py",
|
|
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
missingCount: "1",
|
|
missing: "linuxcnc",
|
|
available: "python3,src/emc/rs274ngc/interp_python.cc,src/emc/pythonplugin/python_plugin.cc,axis/remap/stop-lookahead/python/remap.py,axis/remap/stop-lookahead/python/toplevel.py",
|
|
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_family_host_readiness: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_family_host_readiness: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.target !== expectedRow.target ||
|
|
row.missing_requirement_count !== expectedRow.missingCount ||
|
|
row.missing_host_requirements !== expectedRow.missing ||
|
|
row.available_requirements !== expectedRow.available ||
|
|
row.opt_in_env !== expectedRow.optInEnv
|
|
) {
|
|
throw new Error(`${row.boundary_class}: family host readiness metadata drift`);
|
|
}
|
|
if (
|
|
row.preflight_status !== "blocked_missing_host_runtime" ||
|
|
row.family_host_status !== "blocked_missing_host_requirements"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: family host readiness status drift`);
|
|
}
|
|
if (!row.execution_command.includes(row.opt_in_env)) {
|
|
throw new Error(`${row.boundary_class}: family host readiness command drift`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_class}: family host readiness must not enable execution or promotion`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: family host readiness notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function verifyBrowserBlockedRuntimeHostRequirementParity({
|
|
hostPreflightRows,
|
|
requirementSummaryRows,
|
|
unblockPlanRows,
|
|
familyHostReadinessRows,
|
|
}) {
|
|
const splitList = (value) => value === "-" ? [] : value.split(",");
|
|
const preflightByClass = new Map(hostPreflightRows.map((row) => [row.boundary_class, row]));
|
|
const summaryByRequirement = new Map(requirementSummaryRows.map((row) => [row.requirement, row]));
|
|
const unblockByRequirement = new Map(unblockPlanRows.map((row) => [row.requirement, row]));
|
|
|
|
for (const family of familyHostReadinessRows) {
|
|
const preflight = preflightByClass.get(family.boundary_class);
|
|
if (!preflight) {
|
|
throw new Error(`${family.boundary_class}: missing host preflight parity row`);
|
|
}
|
|
if (
|
|
preflight.target !== family.target ||
|
|
preflight.blocked !== family.blocked ||
|
|
preflight.required_runtime !== family.required_runtime ||
|
|
preflight.opt_in_env !== family.opt_in_env ||
|
|
preflight.execution_command !== family.execution_command ||
|
|
preflight.current_probe_status !== family.current_probe_status ||
|
|
preflight.preflight_status !== family.preflight_status ||
|
|
preflight.missing_requirements !== family.missing_host_requirements
|
|
) {
|
|
throw new Error(`${family.boundary_class}: host preflight/family readiness parity drift`);
|
|
}
|
|
|
|
const missingRequirements = splitList(family.missing_host_requirements);
|
|
const availableRequirements = splitList(family.available_requirements);
|
|
if (family.missing_requirement_count !== String(missingRequirements.length)) {
|
|
throw new Error(`${family.boundary_class}: missing host requirement count drift`);
|
|
}
|
|
if (family.available_requirement_count !== String(availableRequirements.length)) {
|
|
throw new Error(`${family.boundary_class}: available host requirement count drift`);
|
|
}
|
|
|
|
for (const requirement of missingRequirements) {
|
|
const summary = summaryByRequirement.get(requirement);
|
|
const unblock = unblockByRequirement.get(requirement);
|
|
if (!summary || !unblock) {
|
|
throw new Error(`${family.boundary_class}: missing host requirement parity source ${requirement}`);
|
|
}
|
|
if (
|
|
summary.available !== "0" ||
|
|
summary.missing_blocks_runtime !== "1" ||
|
|
!splitList(summary.missing_for).includes(family.boundary_class) ||
|
|
!splitList(summary.blocked_families).includes(family.boundary_class) ||
|
|
!splitList(summary.opt_in_envs).includes(family.opt_in_env)
|
|
) {
|
|
throw new Error(`${family.boundary_class}/${requirement}: host requirement summary parity drift`);
|
|
}
|
|
if (
|
|
unblock.available !== "0" ||
|
|
!splitList(unblock.missing_for).includes(family.boundary_class) ||
|
|
!splitList(unblock.affected_targets).includes(family.target) ||
|
|
!splitList(unblock.opt_in_envs).includes(family.opt_in_env) ||
|
|
!splitList(unblock.affected_execution_commands).includes(family.execution_command) ||
|
|
splitList(unblock.remaining_missing_after_requirement).includes(requirement)
|
|
) {
|
|
throw new Error(`${family.boundary_class}/${requirement}: host unblock plan parity drift`);
|
|
}
|
|
}
|
|
|
|
for (const requirement of availableRequirements) {
|
|
const summary = summaryByRequirement.get(requirement);
|
|
if (!summary) {
|
|
throw new Error(`${family.boundary_class}: missing available requirement summary ${requirement}`);
|
|
}
|
|
if (
|
|
summary.available !== "1" ||
|
|
summary.missing_for !== "-" ||
|
|
summary.missing_blocks_runtime !== "0" ||
|
|
!splitList(summary.required_by).includes(family.boundary_class) ||
|
|
!splitList(summary.blocked_families).includes(family.boundary_class) ||
|
|
!splitList(summary.opt_in_envs).includes(family.opt_in_env)
|
|
) {
|
|
throw new Error(`${family.boundary_class}/${requirement}: available host requirement parity drift`);
|
|
}
|
|
}
|
|
|
|
for (const row of [preflight, family]) {
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${family.boundary_class}: host requirement parity must remain disabled`);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const summary of requirementSummaryRows) {
|
|
if (summary.required_by_count !== String(splitList(summary.required_by).length)) {
|
|
throw new Error(`${summary.requirement}: host requirement required_by_count drift`);
|
|
}
|
|
if (summary.available === "0" && !unblockByRequirement.has(summary.requirement)) {
|
|
throw new Error(`${summary.requirement}: unavailable host requirement lacks unblock plan row`);
|
|
}
|
|
if (summary.available === "1" && unblockByRequirement.has(summary.requirement)) {
|
|
throw new Error(`${summary.requirement}: available host requirement must not have unblock plan row`);
|
|
}
|
|
if (summary.execution_enabled !== "0" || summary.promotion_allowed !== "0") {
|
|
throw new Error(`${summary.requirement}: host requirement parity must remain disabled`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryHostReadinessRollup() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv"),
|
|
);
|
|
if (rows.length !== 1) {
|
|
throw new Error(`browser_runtime_boundary_host_readiness_rollup: row count drift ${rows.length}`);
|
|
}
|
|
|
|
const row = rows[0];
|
|
if (
|
|
row.scope !== "blocked_runtime_host_readiness" ||
|
|
row.family_count !== "3" ||
|
|
row.host_ready_family_count !== "0" ||
|
|
row.host_blocked_family_count !== "3" ||
|
|
row.missing_host_requirement_count !== "4" ||
|
|
row.ready_opt_in_command_count !== "0" ||
|
|
row.ready_opt_in_commands !== "-" ||
|
|
row.host_readiness_status !== "host_blocked_for_all_opt_in_native_probes"
|
|
) {
|
|
throw new Error("host readiness rollup state drift");
|
|
}
|
|
for (const requirement of ["halcmd", "halrun", "linuxcnc", "milltask"]) {
|
|
if (!row.missing_host_requirements.includes(requirement)) {
|
|
throw new Error(`host readiness rollup missing ${requirement}`);
|
|
}
|
|
}
|
|
for (const boundaryClass of ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"]) {
|
|
if (!row.blocked_families.includes(boundaryClass)) {
|
|
throw new Error(`host readiness rollup missing ${boundaryClass}`);
|
|
}
|
|
}
|
|
for (const command of [
|
|
"ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh",
|
|
"ENABLE_TOOL_DB_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_tool_db_runtime.sh",
|
|
"ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh",
|
|
]) {
|
|
if (!row.blocked_opt_in_commands.includes(command)) {
|
|
throw new Error(`host readiness rollup missing blocked command ${command}`);
|
|
}
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error("host readiness rollup must not enable execution or promotion");
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error("host readiness rollup notes drift");
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryOptInProbeDispatchPlan() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-plan.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
missing: "halrun,halcmd,linuxcnc",
|
|
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
target: "axis/db_demo/base.ngc",
|
|
missing: "linuxcnc,milltask,halcmd",
|
|
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
missing: "linuxcnc",
|
|
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_opt_in_probe_dispatch_plan: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_opt_in_probe_dispatch_plan: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.target !== expectedRow.target ||
|
|
row.missing_host_requirements !== expectedRow.missing ||
|
|
row.opt_in_env !== expectedRow.optInEnv
|
|
) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe dispatch metadata drift`);
|
|
}
|
|
if (!row.execution_command.includes(row.opt_in_env)) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe dispatch command drift`);
|
|
}
|
|
if (
|
|
row.family_host_status !== "blocked_missing_host_requirements" ||
|
|
row.host_readiness_status !== "host_blocked_for_all_opt_in_native_probes" ||
|
|
row.host_action !== "skip_missing_host_requirements" ||
|
|
row.dispatch_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe dispatch status drift`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_class}: opt-in probe dispatch must not enable execution or promotion`);
|
|
}
|
|
if (!row.next_action.includes(row.missing_host_requirements)) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe dispatch next action must name missing requirements`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe dispatch notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryOptInProbeDispatchRollup() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-rollup.tsv"),
|
|
);
|
|
if (rows.length !== 1) {
|
|
throw new Error(`browser_runtime_boundary_opt_in_probe_dispatch_rollup: row count drift ${rows.length}`);
|
|
}
|
|
|
|
const row = rows[0];
|
|
if (
|
|
row.scope !== "blocked_runtime_opt_in_probe_dispatch" ||
|
|
row.probe_count !== "3" ||
|
|
row.dispatch_allowed_count !== "0" ||
|
|
row.dispatch_blocked_count !== "3" ||
|
|
row.dispatch_actions !== "skip_missing_host_requirements" ||
|
|
row.dispatch_allowed_commands !== "-" ||
|
|
row.dispatch_status !== "dispatch_blocked_for_all_opt_in_native_probes"
|
|
) {
|
|
throw new Error("opt-in probe dispatch rollup state drift");
|
|
}
|
|
for (const boundaryClass of ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"]) {
|
|
if (!row.blocked_families.includes(boundaryClass)) {
|
|
throw new Error(`opt-in probe dispatch rollup missing ${boundaryClass}`);
|
|
}
|
|
}
|
|
for (const requirement of ["halcmd", "halrun", "linuxcnc", "milltask"]) {
|
|
if (!row.missing_host_requirements.includes(requirement)) {
|
|
throw new Error(`opt-in probe dispatch rollup missing ${requirement}`);
|
|
}
|
|
}
|
|
for (const command of [
|
|
"ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_millturn_user_m_runtime.sh",
|
|
"ENABLE_TOOL_DB_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_tool_db_runtime.sh",
|
|
"ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1 bash wasm-port/tests/native/probe_python_remap_runtime.sh",
|
|
]) {
|
|
if (!row.dispatch_blocked_commands.includes(command)) {
|
|
throw new Error(`opt-in probe dispatch rollup missing blocked command ${command}`);
|
|
}
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error("opt-in probe dispatch rollup must not enable execution or promotion");
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error("opt-in probe dispatch rollup notes drift");
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryOptInProbeSkipEvidenceContract() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-contract.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
|
|
missing: "halrun,halcmd,linuxcnc",
|
|
optInEnv: "ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
target: "axis/db_demo/base.ngc",
|
|
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
|
|
missing: "linuxcnc,milltask,halcmd",
|
|
optInEnv: "ENABLE_TOOL_DB_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
|
|
missing: "linuxcnc",
|
|
optInEnv: "ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_opt_in_probe_skip_evidence_contract: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_opt_in_probe_skip_evidence_contract: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (
|
|
row.target !== expectedRow.target ||
|
|
row.runtime_probe !== expectedRow.runtimeProbe ||
|
|
row.missing_host_requirements !== expectedRow.missing ||
|
|
row.opt_in_env !== expectedRow.optInEnv
|
|
) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe skip evidence metadata drift`);
|
|
}
|
|
if (!row.execution_command.includes(row.opt_in_env)) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe skip evidence command drift`);
|
|
}
|
|
if (
|
|
row.host_action !== "skip_missing_host_requirements" ||
|
|
row.skip_reason !== "missing_host_requirements" ||
|
|
row.current_probe_status !== "skipped_missing_host_runtime" ||
|
|
row.evidence_required_now !== "0" ||
|
|
row.observed_evidence_ready !== "0" ||
|
|
row.native_evidence_status !== "pending_until_native_pass" ||
|
|
row.skip_evidence_status !== "skip_valid_until_host_requirements_available"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe skip evidence status drift`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_class}: opt-in probe skip evidence must not enable execution or promotion`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: opt-in probe skip evidence notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function verifyBrowserBlockedRuntimeProbeExecutionParity({
|
|
executionPlanRows,
|
|
passEvidenceRows,
|
|
hostPreflightRows,
|
|
dispatchPlanRows,
|
|
skipEvidenceRows,
|
|
}) {
|
|
const expectedClasses = [
|
|
"L4-USER-M-PROCESS",
|
|
"L4-TOOL-DB",
|
|
"L4-PYTHON-REMAP",
|
|
];
|
|
const passByClass = new Map(passEvidenceRows.map((row) => [row.boundary_class, row]));
|
|
const preflightByClass = new Map(hostPreflightRows.map((row) => [row.boundary_class, row]));
|
|
const dispatchByClass = new Map(dispatchPlanRows.map((row) => [row.boundary_class, row]));
|
|
const skipByClass = new Map(skipEvidenceRows.map((row) => [row.boundary_class, row]));
|
|
|
|
for (const rows of [
|
|
executionPlanRows,
|
|
passEvidenceRows,
|
|
hostPreflightRows,
|
|
dispatchPlanRows,
|
|
skipEvidenceRows,
|
|
]) {
|
|
if (rows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",")) {
|
|
throw new Error("browser_blocked_runtime_probe_execution_parity: class order drift");
|
|
}
|
|
}
|
|
|
|
for (const execution of executionPlanRows) {
|
|
const pass = passByClass.get(execution.boundary_class);
|
|
const preflight = preflightByClass.get(execution.boundary_class);
|
|
const dispatch = dispatchByClass.get(execution.boundary_class);
|
|
const skip = skipByClass.get(execution.boundary_class);
|
|
if (!pass || !preflight || !dispatch || !skip) {
|
|
throw new Error(`${execution.boundary_class}: missing probe execution parity row`);
|
|
}
|
|
|
|
for (const row of [pass, preflight, dispatch, skip]) {
|
|
if (row.boundary_kind !== execution.boundary_kind || row.target !== execution.target) {
|
|
throw new Error(`${execution.boundary_class}: probe execution kind/target drift`);
|
|
}
|
|
}
|
|
if (
|
|
preflight.blocked !== execution.boundary_class ||
|
|
dispatch.blocked !== execution.boundary_class ||
|
|
skip.blocked !== execution.boundary_class
|
|
) {
|
|
throw new Error(`${execution.boundary_class}: probe execution blocked kind drift`);
|
|
}
|
|
if (
|
|
pass.runtime_probe !== execution.runtime_probe ||
|
|
skip.runtime_probe !== execution.runtime_probe ||
|
|
pass.expected_pass_status !== execution.expected_pass_status
|
|
) {
|
|
throw new Error(`${execution.boundary_class}: probe execution runtime/pass status drift`);
|
|
}
|
|
if (preflight.required_native_proof !== execution.required_native_proof) {
|
|
throw new Error(`${execution.boundary_class}: probe execution required native proof drift`);
|
|
}
|
|
for (const row of [preflight, dispatch, skip]) {
|
|
if (
|
|
row.opt_in_env !== execution.opt_in_env ||
|
|
row.execution_command !== execution.execution_command
|
|
) {
|
|
throw new Error(`${execution.boundary_class}: probe execution opt-in command drift`);
|
|
}
|
|
}
|
|
if (
|
|
pass.current_probe_status !== execution.current_probe_status ||
|
|
preflight.current_probe_status !== execution.current_probe_status ||
|
|
skip.current_probe_status !== execution.current_probe_status
|
|
) {
|
|
throw new Error(`${execution.boundary_class}: probe execution current status drift`);
|
|
}
|
|
if (
|
|
preflight.runtime_ready !== execution.runtime_ready ||
|
|
preflight.source_proof_ready !== execution.source_proof_ready ||
|
|
preflight.missing_requirements !== execution.missing_requirements ||
|
|
dispatch.missing_host_requirements !== execution.missing_requirements ||
|
|
skip.missing_host_requirements !== execution.missing_requirements
|
|
) {
|
|
throw new Error(`${execution.boundary_class}: probe execution readiness/missing requirement drift`);
|
|
}
|
|
|
|
if (
|
|
execution.runtime_ready !== "0" ||
|
|
execution.source_proof_ready !== "1" ||
|
|
execution.current_probe_status !== "skipped_missing_host_runtime" ||
|
|
execution.plan_status !== "blocked_missing_host_runtime" ||
|
|
preflight.preflight_status !== "blocked_missing_host_runtime" ||
|
|
dispatch.host_readiness_status !== "host_blocked_for_all_opt_in_native_probes" ||
|
|
dispatch.host_action !== "skip_missing_host_requirements" ||
|
|
dispatch.dispatch_allowed !== "0" ||
|
|
skip.host_action !== "skip_missing_host_requirements" ||
|
|
skip.skip_reason !== "missing_host_requirements"
|
|
) {
|
|
throw new Error(`${execution.boundary_class}: probe execution host-blocked status drift`);
|
|
}
|
|
if (
|
|
pass.evidence_required_now !== "0" ||
|
|
skip.evidence_required_now !== "0" ||
|
|
pass.observed_evidence_ready !== "0" ||
|
|
skip.observed_evidence_ready !== "0" ||
|
|
pass.evidence_status !== "pending_until_native_pass" ||
|
|
skip.native_evidence_status !== "pending_until_native_pass" ||
|
|
skip.skip_evidence_status !== "skip_valid_until_host_requirements_available"
|
|
) {
|
|
throw new Error(`${execution.boundary_class}: probe execution evidence status drift`);
|
|
}
|
|
for (const row of [execution, pass, preflight, dispatch, skip]) {
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${execution.boundary_class}: probe execution parity must remain disabled`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryOptInProbeSkipEvidenceRollup() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv"),
|
|
);
|
|
if (rows.length !== 1) {
|
|
throw new Error(`browser_runtime_boundary_opt_in_probe_skip_evidence_rollup: row count drift ${rows.length}`);
|
|
}
|
|
|
|
const row = rows[0];
|
|
if (
|
|
row.scope !== "blocked_runtime_opt_in_probe_skip_evidence" ||
|
|
row.probe_count !== "3" ||
|
|
row.skip_count !== "3" ||
|
|
row.evidence_required_now_count !== "0" ||
|
|
row.observed_evidence_ready_count !== "0" ||
|
|
row.skip_reasons !== "missing_host_requirements" ||
|
|
row.current_probe_statuses !== "skipped_missing_host_runtime" ||
|
|
row.native_evidence_statuses !== "pending_until_native_pass" ||
|
|
row.skip_evidence_statuses !== "skip_valid_until_host_requirements_available" ||
|
|
row.evidence_rollup_status !== "no_native_pass_evidence_accepted_while_host_blocked"
|
|
) {
|
|
throw new Error("opt-in probe skip evidence rollup state drift");
|
|
}
|
|
for (const boundaryClass of ["L4-PYTHON-REMAP", "L4-TOOL-DB", "L4-USER-M-PROCESS"]) {
|
|
if (!row.skipped_families.includes(boundaryClass)) {
|
|
throw new Error(`opt-in probe skip evidence rollup missing ${boundaryClass}`);
|
|
}
|
|
}
|
|
for (const requirement of ["halcmd", "halrun", "linuxcnc", "milltask"]) {
|
|
if (!row.missing_host_requirements.includes(requirement)) {
|
|
throw new Error(`opt-in probe skip evidence rollup missing ${requirement}`);
|
|
}
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error("opt-in probe skip evidence rollup must not enable execution or promotion");
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error("opt-in probe skip evidence rollup notes drift");
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function verifyBrowserBlockedRuntimeRollupParity({
|
|
hostReadinessRollupRows,
|
|
dispatchRollupRows,
|
|
skipEvidenceRollupRows,
|
|
familyHostReadinessRows,
|
|
dispatchPlanRows,
|
|
skipEvidenceRows,
|
|
}) {
|
|
if (
|
|
hostReadinessRollupRows.length !== 1 ||
|
|
dispatchRollupRows.length !== 1 ||
|
|
skipEvidenceRollupRows.length !== 1
|
|
) {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: expected one row per rollup");
|
|
}
|
|
|
|
const host = hostReadinessRollupRows[0];
|
|
const dispatch = dispatchRollupRows[0];
|
|
const skip = skipEvidenceRollupRows[0];
|
|
|
|
if (
|
|
host.family_count !== String(familyHostReadinessRows.length) ||
|
|
dispatch.probe_count !== String(dispatchPlanRows.length) ||
|
|
skip.probe_count !== String(skipEvidenceRows.length) ||
|
|
host.family_count !== dispatch.probe_count ||
|
|
dispatch.probe_count !== skip.probe_count
|
|
) {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: count drift");
|
|
}
|
|
if (
|
|
host.host_ready_family_count !== dispatch.dispatch_allowed_count ||
|
|
host.host_blocked_family_count !== dispatch.dispatch_blocked_count ||
|
|
dispatch.dispatch_blocked_count !== skip.skip_count
|
|
) {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: blocked/allowed count drift");
|
|
}
|
|
if (
|
|
host.blocked_families !== dispatch.blocked_families ||
|
|
dispatch.blocked_families !== skip.skipped_families
|
|
) {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: blocked family drift");
|
|
}
|
|
if (
|
|
host.missing_host_requirements !== dispatch.missing_host_requirements ||
|
|
dispatch.missing_host_requirements !== skip.missing_host_requirements
|
|
) {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: missing requirement drift");
|
|
}
|
|
if (
|
|
host.ready_opt_in_commands !== dispatch.dispatch_allowed_commands ||
|
|
host.blocked_opt_in_commands !== dispatch.dispatch_blocked_commands
|
|
) {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: opt-in command drift");
|
|
}
|
|
if (
|
|
host.host_readiness_status !== "host_blocked_for_all_opt_in_native_probes" ||
|
|
dispatch.dispatch_status !== "dispatch_blocked_for_all_opt_in_native_probes" ||
|
|
skip.evidence_rollup_status !== "no_native_pass_evidence_accepted_while_host_blocked" ||
|
|
skip.current_probe_statuses !== "skipped_missing_host_runtime" ||
|
|
skip.native_evidence_statuses !== "pending_until_native_pass" ||
|
|
skip.skip_evidence_statuses !== "skip_valid_until_host_requirements_available" ||
|
|
skip.evidence_required_now_count !== "0" ||
|
|
skip.observed_evidence_ready_count !== "0"
|
|
) {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: host-blocked status drift");
|
|
}
|
|
for (const row of [host, dispatch, skip]) {
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: rollup must remain disabled");
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error("browser_blocked_runtime_rollup_parity: rollup notes drift");
|
|
}
|
|
}
|
|
}
|
|
|
|
async function verifyBrowserRuntimeBoundaryNativeEvidenceAcceptanceGate() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/runtime-boundary-native-evidence-acceptance-gate.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"L4-USER-M-PROCESS",
|
|
{
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
runtimeProbe: "linuxcnc_millturn_user_m_runtime_probe",
|
|
missing: "halrun,halcmd,linuxcnc",
|
|
},
|
|
],
|
|
[
|
|
"L4-TOOL-DB",
|
|
{
|
|
target: "axis/db_demo/base.ngc",
|
|
runtimeProbe: "linuxcnc_tool_db_runtime_probe",
|
|
missing: "linuxcnc,milltask,halcmd",
|
|
},
|
|
],
|
|
[
|
|
"L4-PYTHON-REMAP",
|
|
{
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
runtimeProbe: "linuxcnc_python_remap_runtime_probe",
|
|
missing: "linuxcnc",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_runtime_boundary_native_evidence_acceptance_gate: row count drift ${rows.length}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_class);
|
|
if (!expectedRow) {
|
|
throw new Error(`browser_runtime_boundary_native_evidence_acceptance_gate: unexpected ${row.boundary_class}`);
|
|
}
|
|
if (row.target !== expectedRow.target || row.runtime_probe !== expectedRow.runtimeProbe) {
|
|
throw new Error(`${row.boundary_class}: native evidence acceptance gate metadata drift`);
|
|
}
|
|
if (
|
|
row.current_probe_status !== "skipped_missing_host_runtime" ||
|
|
row.native_evidence_status !== "pending_until_native_pass" ||
|
|
row.evidence_required_now !== "0" ||
|
|
row.observed_evidence_ready !== "0" ||
|
|
row.skip_evidence_status !== "skip_valid_until_host_requirements_available" ||
|
|
row.promotion_ready !== "0" ||
|
|
row.node_gate_status !== "blocked_until_native_pass_evidence" ||
|
|
row.browser_gate_status !== "blocked_until_node_gate_complete" ||
|
|
row.native_evidence_gate !== "blocked_until_host_requirements_available" ||
|
|
row.evidence_acceptance_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.boundary_class}: native evidence acceptance gate status drift`);
|
|
}
|
|
if (!row.next_action.includes(expectedRow.missing)) {
|
|
throw new Error(`${row.boundary_class}: native evidence acceptance gate next action must name missing requirements`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_class}: native evidence acceptance gate must not enable execution or promotion`);
|
|
}
|
|
if (!row.notes.includes("no_automatic_promotion")) {
|
|
throw new Error(`${row.boundary_class}: native evidence acceptance gate notes drift`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
async function verifyBrowserNextBoundaryRecommendations() {
|
|
const rows = parseTsv(
|
|
await fetchText("../../build/wasm/sim-configs-inventory/next-boundary-recommendations.tsv"),
|
|
);
|
|
const expected = new Map([
|
|
[
|
|
"external_user_m_process",
|
|
{
|
|
target: "axis/vismach/millturn/example.ngc",
|
|
blocked: "L4-USER-M-PROCESS",
|
|
recommendation: "implement_native_m128_m129_state_probe",
|
|
primaryArtifact: "user-m-process-native-runtime-probe-gate.tsv",
|
|
proofToken: "not_user_m_event_only",
|
|
},
|
|
],
|
|
[
|
|
"tool_database_process",
|
|
{
|
|
target: "axis/db_demo/base.ngc",
|
|
blocked: "L4-TOOL-DB",
|
|
recommendation: "implement_tooldata_db_protocol_probe",
|
|
primaryArtifact: "tool-db-process-native-runtime-probe-gate.tsv",
|
|
proofToken: "not_tbl_fallback",
|
|
},
|
|
],
|
|
[
|
|
"python_runtime",
|
|
{
|
|
target: "axis/remap/stop-lookahead/nc_files",
|
|
blocked: "L4-PYTHON-REMAP",
|
|
recommendation: "implement_minimal_python_runtime_lifecycle_probe",
|
|
primaryArtifact: "python-remap-native-runtime-fixture-plan.tsv",
|
|
proofToken: "not_js_semantics",
|
|
},
|
|
],
|
|
]);
|
|
|
|
if (rows.length !== expected.size) {
|
|
throw new Error(`browser_next_boundary_recommendations: row count drift ${rows.length}`);
|
|
}
|
|
if (rows.map((row) => row.priority).join(",") !== "1,2,3") {
|
|
throw new Error("browser_next_boundary_recommendations: priority order drift");
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const expectedRow = expected.get(row.boundary_kind);
|
|
if (!expectedRow) {
|
|
throw new Error(`${row.boundary_kind}: unexpected recommendation boundary kind`);
|
|
}
|
|
if (
|
|
row.target !== expectedRow.target ||
|
|
row.blocked !== expectedRow.blocked ||
|
|
row.recommendation !== expectedRow.recommendation ||
|
|
row.primary_artifact !== expectedRow.primaryArtifact
|
|
) {
|
|
throw new Error(`${row.boundary_kind}: recommendation identity drift`);
|
|
}
|
|
if (!/^[1-9][0-9]*$/.test(row.artifact_row_count)) {
|
|
throw new Error(`${row.boundary_kind}: invalid recommendation artifact row count`);
|
|
}
|
|
if (
|
|
row.promotion_lock_active !== "1" ||
|
|
row.contract_ok !== "1" ||
|
|
row.execution_enabled !== "0" ||
|
|
row.promotion_allowed !== "0"
|
|
) {
|
|
throw new Error(`${row.boundary_kind}: recommendation must keep active lock and disabled execution`);
|
|
}
|
|
if (
|
|
row.required_native_proof === "-" ||
|
|
!row.required_node_proof.includes(expectedRow.proofToken) ||
|
|
row.required_browser_proof === "-"
|
|
) {
|
|
throw new Error(`${row.boundary_kind}: recommendation proof drift`);
|
|
}
|
|
if (
|
|
!row.source_artifacts.includes("next-boundary-worklist.tsv") ||
|
|
!row.source_artifacts.includes("blocked-runtime-promotion-lock.tsv")
|
|
) {
|
|
throw new Error(`${row.boundary_kind}: recommendation lacks source artifact trace`);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function verifyBrowserNextBoundaryRecommendationCountParity({
|
|
recommendationRows,
|
|
userMRuntimeProbeGateRows,
|
|
toolDbRuntimeProbeGateRows,
|
|
pythonRuntimeContractRows,
|
|
}) {
|
|
const expectedCounts = new Map([
|
|
["external_user_m_process", userMRuntimeProbeGateRows.length],
|
|
["tool_database_process", toolDbRuntimeProbeGateRows.length],
|
|
["python_runtime", pythonRuntimeContractRows.length],
|
|
]);
|
|
|
|
for (const row of recommendationRows) {
|
|
const expectedCount = expectedCounts.get(row.boundary_kind);
|
|
if (expectedCount === undefined) {
|
|
throw new Error(`${row.boundary_kind}: recommendation count parity unexpected kind`);
|
|
}
|
|
if (row.artifact_row_count !== String(expectedCount)) {
|
|
throw new Error(`${row.boundary_kind}: recommendation artifact count ${row.artifact_row_count} drift from generated source count ${expectedCount}`);
|
|
}
|
|
if (row.boundary_kind === "external_user_m_process" && !row.source_artifacts.includes("user-m-process-native-runtime-probe-gate.tsv")) {
|
|
throw new Error("user-M recommendation lacks runtime probe gate source artifact");
|
|
}
|
|
if (row.boundary_kind === "tool_database_process" && !row.source_artifacts.includes("tool-db-process-native-runtime-probe-gate.tsv")) {
|
|
throw new Error("tool DB recommendation lacks runtime probe gate source artifact");
|
|
}
|
|
if (row.boundary_kind === "python_runtime" && !row.source_artifacts.includes("python-remap-runtime-contract.tsv")) {
|
|
throw new Error("Python recommendation lacks runtime contract source artifact");
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_kind}: recommendation count parity must remain disabled`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyBrowserNextBoundaryRecommendationSourceArtifactCoverage({
|
|
recommendationRows,
|
|
wasmArtifactNames,
|
|
}) {
|
|
const artifactNameSet = new Set(wasmArtifactNames);
|
|
const expectedSourceArtifacts = new Map([
|
|
[
|
|
"external_user_m_process",
|
|
[
|
|
"next-boundary-worklist.tsv",
|
|
"blocked-runtime-promotion-lock.tsv",
|
|
"user-m-process-transition-plan.tsv",
|
|
"user-m-process-native-runtime-state-plan.tsv",
|
|
"user-m-process-native-runtime-probe-gate.tsv",
|
|
],
|
|
],
|
|
[
|
|
"tool_database_process",
|
|
[
|
|
"next-boundary-worklist.tsv",
|
|
"blocked-runtime-promotion-lock.tsv",
|
|
"tool-db-process-transaction-plan.tsv",
|
|
"tool-db-process-native-runtime-readiness.tsv",
|
|
"tool-db-process-native-runtime-probe-gate.tsv",
|
|
],
|
|
],
|
|
[
|
|
"python_runtime",
|
|
[
|
|
"next-boundary-worklist.tsv",
|
|
"blocked-runtime-promotion-lock.tsv",
|
|
"python-remap-runtime-contract.tsv",
|
|
"python-remap-native-runtime-fixture-plan.tsv",
|
|
"python-remap-native-runtime-probe-gate.tsv",
|
|
],
|
|
],
|
|
]);
|
|
|
|
for (const row of recommendationRows) {
|
|
const sourceArtifacts = row.source_artifacts === "-" ? [] : row.source_artifacts.split(",");
|
|
const expectedSources = expectedSourceArtifacts.get(row.boundary_kind);
|
|
if (!expectedSources) {
|
|
throw new Error(`${row.boundary_kind}: unexpected recommendation source artifact kind`);
|
|
}
|
|
if (sourceArtifacts.length === 0) {
|
|
throw new Error(`${row.boundary_kind}: recommendation lacks source artifacts`);
|
|
}
|
|
if (sourceArtifacts.join(",") !== expectedSources.join(",")) {
|
|
throw new Error(`${row.boundary_kind}: recommendation source artifact set drift`);
|
|
}
|
|
if (new Set(sourceArtifacts).size !== sourceArtifacts.length) {
|
|
throw new Error(`${row.boundary_kind}: duplicate recommendation source artifact`);
|
|
}
|
|
if (!sourceArtifacts.includes(row.primary_artifact)) {
|
|
throw new Error(`${row.boundary_kind}: recommendation primary artifact missing from source artifacts`);
|
|
}
|
|
for (const artifactName of [row.primary_artifact, ...sourceArtifacts]) {
|
|
if (!artifactNameSet.has(artifactName)) {
|
|
throw new Error(`${row.boundary_kind}: unknown recommendation source artifact ${artifactName}`);
|
|
}
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${row.boundary_kind}: recommendation source artifact coverage must remain disabled`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyBrowserBlockedRuntimePromotionGateParity({
|
|
promotionLockRows,
|
|
promotionReadinessRows,
|
|
promotionBlockerRows,
|
|
postNativePassGateRows,
|
|
nativeEvidenceAcceptanceGateRows,
|
|
passEvidenceRows,
|
|
}) {
|
|
const expectedClasses = [
|
|
"L4-USER-M-PROCESS",
|
|
"L4-TOOL-DB",
|
|
"L4-PYTHON-REMAP",
|
|
];
|
|
const lockByTarget = new Map(promotionLockRows.map((row) => [row.target, row]));
|
|
const readinessByClass = new Map(promotionReadinessRows.map((row) => [row.boundary_class, row]));
|
|
const blockerByClass = new Map(promotionBlockerRows.map((row) => [row.boundary_class, row]));
|
|
const postNativeByClass = new Map(postNativePassGateRows.map((row) => [row.boundary_class, row]));
|
|
const evidenceGateByClass = new Map(
|
|
nativeEvidenceAcceptanceGateRows.map((row) => [row.boundary_class, row]),
|
|
);
|
|
const passEvidenceByClass = new Map(passEvidenceRows.map((row) => [row.boundary_class, row]));
|
|
|
|
for (const rows of [
|
|
promotionReadinessRows,
|
|
promotionBlockerRows,
|
|
postNativePassGateRows,
|
|
nativeEvidenceAcceptanceGateRows,
|
|
passEvidenceRows,
|
|
]) {
|
|
if (rows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",")) {
|
|
throw new Error("browser_blocked_runtime_promotion_gate_parity: class order drift");
|
|
}
|
|
}
|
|
|
|
for (const boundaryClass of expectedClasses) {
|
|
const readiness = readinessByClass.get(boundaryClass);
|
|
const blocker = blockerByClass.get(boundaryClass);
|
|
const postNative = postNativeByClass.get(boundaryClass);
|
|
const evidenceGate = evidenceGateByClass.get(boundaryClass);
|
|
const passEvidence = passEvidenceByClass.get(boundaryClass);
|
|
if (!readiness || !blocker || !postNative || !evidenceGate || !passEvidence) {
|
|
throw new Error(`${boundaryClass}: missing promotion gate parity row`);
|
|
}
|
|
|
|
const lock = lockByTarget.get(readiness.target);
|
|
if (!lock) {
|
|
throw new Error(`${boundaryClass}: missing promotion lock row for ${readiness.target}`);
|
|
}
|
|
|
|
for (const row of [blocker, postNative, evidenceGate, passEvidence]) {
|
|
if (row.boundary_kind !== readiness.boundary_kind || row.target !== readiness.target) {
|
|
throw new Error(`${boundaryClass}: promotion gate kind/target drift`);
|
|
}
|
|
}
|
|
for (const row of [blocker, postNative, evidenceGate]) {
|
|
if (row.blocked !== readiness.blocked) {
|
|
throw new Error(`${boundaryClass}: promotion gate blocked kind drift`);
|
|
}
|
|
}
|
|
|
|
if (
|
|
readiness.current_probe_status !== passEvidence.current_probe_status ||
|
|
readiness.expected_pass_status !== passEvidence.expected_pass_status ||
|
|
readiness.native_evidence_status !== passEvidence.evidence_status ||
|
|
readiness.native_evidence_ready !== passEvidence.observed_evidence_ready ||
|
|
readiness.promotion_lock_active !== lock.lock_active ||
|
|
readiness.promotion_ready !== blocker.promotion_ready ||
|
|
readiness.promotion_ready !== evidenceGate.promotion_ready
|
|
) {
|
|
throw new Error(`${boundaryClass}: promotion readiness/pass/lock drift`);
|
|
}
|
|
if (
|
|
postNative.current_probe_status !== readiness.current_probe_status ||
|
|
postNative.expected_pass_status !== readiness.expected_pass_status ||
|
|
postNative.native_pass_ready !== readiness.native_pass_ready ||
|
|
postNative.native_evidence_ready !== readiness.native_evidence_ready ||
|
|
postNative.manual_lock_update_required !== readiness.manual_lock_update_required ||
|
|
postNative.promotion_lock_active !== readiness.promotion_lock_active
|
|
) {
|
|
throw new Error(`${boundaryClass}: post-native promotion gate drift`);
|
|
}
|
|
if (
|
|
evidenceGate.current_probe_status !== readiness.current_probe_status ||
|
|
evidenceGate.native_evidence_status !== readiness.native_evidence_status ||
|
|
evidenceGate.evidence_required_now !== passEvidence.evidence_required_now ||
|
|
evidenceGate.observed_evidence_ready !== passEvidence.observed_evidence_ready ||
|
|
evidenceGate.node_gate_status !== postNative.node_gate_status ||
|
|
evidenceGate.browser_gate_status !== postNative.browser_gate_status
|
|
) {
|
|
throw new Error(`${boundaryClass}: native evidence acceptance parity drift`);
|
|
}
|
|
|
|
if (
|
|
readiness.native_pass_ready !== "0" ||
|
|
readiness.native_evidence_ready !== "0" ||
|
|
readiness.node_inventory_gate_complete !== "0" ||
|
|
readiness.browser_smoke_gate_complete !== "0" ||
|
|
readiness.promotion_lock_active !== "1" ||
|
|
readiness.manual_lock_update_required !== "1" ||
|
|
readiness.promotion_ready !== "0" ||
|
|
postNative.node_gate_status !== "blocked_until_native_pass_evidence" ||
|
|
postNative.browser_gate_status !== "blocked_until_node_gate_complete" ||
|
|
postNative.gate_status !== "blocked_before_native_pass" ||
|
|
evidenceGate.native_evidence_gate !== "blocked_until_host_requirements_available" ||
|
|
evidenceGate.evidence_acceptance_allowed !== "0"
|
|
) {
|
|
throw new Error(`${boundaryClass}: promotion gate must remain host-blocked`);
|
|
}
|
|
for (const blockerKey of [
|
|
"native_runtime_probe_not_passed",
|
|
"native_pass_evidence_not_ready",
|
|
"node_inventory_gate_not_complete",
|
|
"browser_smoke_gate_not_complete",
|
|
"promotion_lock_active",
|
|
"manual_lock_update_required",
|
|
]) {
|
|
if (!blocker.blocker_keys.includes(blockerKey)) {
|
|
throw new Error(`${boundaryClass}: promotion blocker missing ${blockerKey}`);
|
|
}
|
|
}
|
|
for (const row of [readiness, blocker, postNative, evidenceGate, passEvidence, lock]) {
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${boundaryClass}: promotion gate parity must remain disabled`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyBrowserBlockedRuntimeOptInGateParity({
|
|
recommendationRows,
|
|
executionPlanRows,
|
|
promotionReadinessRows,
|
|
promotionBlockerRows,
|
|
postNativePassGateRows,
|
|
hostPreflightRows,
|
|
familyHostReadinessRows,
|
|
dispatchPlanRows,
|
|
skipEvidenceRows,
|
|
evidenceAcceptanceGateRows,
|
|
}) {
|
|
const expectedClasses = [
|
|
"L4-USER-M-PROCESS",
|
|
"L4-TOOL-DB",
|
|
"L4-PYTHON-REMAP",
|
|
];
|
|
const classToKind = new Map([
|
|
["L4-USER-M-PROCESS", "external_user_m_process"],
|
|
["L4-TOOL-DB", "tool_database_process"],
|
|
["L4-PYTHON-REMAP", "python_runtime"],
|
|
]);
|
|
const recommendationByClass = new Map(recommendationRows.map((row) => [row.blocked, row]));
|
|
const byClass = (rows) => new Map(rows.map((row) => [row.boundary_class, row]));
|
|
const executionByClass = byClass(executionPlanRows);
|
|
const readinessByClass = byClass(promotionReadinessRows);
|
|
const blockerByClass = byClass(promotionBlockerRows);
|
|
const postNativeByClass = byClass(postNativePassGateRows);
|
|
const preflightByClass = byClass(hostPreflightRows);
|
|
const familyReadinessByClass = byClass(familyHostReadinessRows);
|
|
const dispatchByClass = byClass(dispatchPlanRows);
|
|
const skipEvidenceByClass = byClass(skipEvidenceRows);
|
|
const evidenceGateByClass = byClass(evidenceAcceptanceGateRows);
|
|
|
|
for (const rows of [
|
|
executionPlanRows,
|
|
promotionReadinessRows,
|
|
promotionBlockerRows,
|
|
postNativePassGateRows,
|
|
hostPreflightRows,
|
|
familyHostReadinessRows,
|
|
dispatchPlanRows,
|
|
skipEvidenceRows,
|
|
evidenceAcceptanceGateRows,
|
|
]) {
|
|
if (rows.map((row) => row.boundary_class).join(",") !== expectedClasses.join(",")) {
|
|
throw new Error("browser_blocked_runtime_opt_in_gate_parity: class order drift");
|
|
}
|
|
}
|
|
|
|
for (const boundaryClass of expectedClasses) {
|
|
const expectedKind = classToKind.get(boundaryClass);
|
|
const recommendation = recommendationByClass.get(boundaryClass);
|
|
const execution = executionByClass.get(boundaryClass);
|
|
const readiness = readinessByClass.get(boundaryClass);
|
|
const blocker = blockerByClass.get(boundaryClass);
|
|
const postNative = postNativeByClass.get(boundaryClass);
|
|
const preflight = preflightByClass.get(boundaryClass);
|
|
const familyReadiness = familyReadinessByClass.get(boundaryClass);
|
|
const dispatch = dispatchByClass.get(boundaryClass);
|
|
const skipEvidence = skipEvidenceByClass.get(boundaryClass);
|
|
const evidenceGate = evidenceGateByClass.get(boundaryClass);
|
|
for (const [label, row] of [
|
|
["recommendation", recommendation],
|
|
["execution", execution],
|
|
["readiness", readiness],
|
|
["blocker", blocker],
|
|
["postNative", postNative],
|
|
["preflight", preflight],
|
|
["familyReadiness", familyReadiness],
|
|
["dispatch", dispatch],
|
|
["skipEvidence", skipEvidence],
|
|
["evidenceGate", evidenceGate],
|
|
]) {
|
|
if (!row) {
|
|
throw new Error(`${boundaryClass}: missing ${label} row for opt-in parity`);
|
|
}
|
|
}
|
|
|
|
if (recommendation.boundary_kind !== expectedKind) {
|
|
throw new Error(`${boundaryClass}: recommendation kind drift`);
|
|
}
|
|
for (const row of [
|
|
execution,
|
|
readiness,
|
|
blocker,
|
|
postNative,
|
|
preflight,
|
|
familyReadiness,
|
|
dispatch,
|
|
skipEvidence,
|
|
evidenceGate,
|
|
]) {
|
|
if (
|
|
row.boundary_kind !== expectedKind ||
|
|
row.target !== recommendation.target ||
|
|
(row.blocked || row.boundary_class) !== recommendation.blocked
|
|
) {
|
|
throw new Error(`${boundaryClass}: opt-in gate identity drift`);
|
|
}
|
|
if (row.execution_enabled !== "0" || row.promotion_allowed !== "0") {
|
|
throw new Error(`${boundaryClass}: opt-in gate parity must remain disabled`);
|
|
}
|
|
}
|
|
|
|
if (
|
|
execution.required_native_proof !== recommendation.required_native_proof ||
|
|
postNative.required_node_proof !== recommendation.required_node_proof ||
|
|
postNative.required_browser_proof !== recommendation.required_browser_proof ||
|
|
preflight.required_native_proof !== execution.required_native_proof
|
|
) {
|
|
throw new Error(`${boundaryClass}: opt-in gate proof drift`);
|
|
}
|
|
if (
|
|
preflight.opt_in_env !== execution.opt_in_env ||
|
|
dispatch.opt_in_env !== execution.opt_in_env ||
|
|
skipEvidence.opt_in_env !== execution.opt_in_env ||
|
|
preflight.execution_command !== execution.execution_command ||
|
|
dispatch.execution_command !== execution.execution_command ||
|
|
skipEvidence.execution_command !== execution.execution_command
|
|
) {
|
|
throw new Error(`${boundaryClass}: opt-in gate command drift`);
|
|
}
|
|
if (
|
|
preflight.current_probe_status !== execution.current_probe_status ||
|
|
readiness.current_probe_status !== execution.current_probe_status ||
|
|
postNative.current_probe_status !== execution.current_probe_status ||
|
|
evidenceGate.current_probe_status !== execution.current_probe_status ||
|
|
familyReadiness.current_probe_status !== execution.current_probe_status
|
|
) {
|
|
throw new Error(`${boundaryClass}: opt-in gate probe status drift`);
|
|
}
|
|
if (
|
|
preflight.missing_requirements !== execution.missing_requirements ||
|
|
dispatch.missing_host_requirements !== execution.missing_requirements ||
|
|
skipEvidence.missing_host_requirements !== execution.missing_requirements ||
|
|
blocker.missing_runtime_requirements !== execution.missing_requirements
|
|
) {
|
|
throw new Error(`${boundaryClass}: opt-in gate missing requirement drift`);
|
|
}
|
|
if (
|
|
evidenceGate.evidence_acceptance_allowed !== "0" ||
|
|
dispatch.dispatch_allowed !== "0" ||
|
|
readiness.promotion_lock_active !== "1" ||
|
|
postNative.promotion_lock_active !== "1" ||
|
|
evidenceGate.native_evidence_gate.indexOf("blocked") === -1 ||
|
|
!blocker.blocker_keys.includes("promotion_lock_active") ||
|
|
!blocker.blocker_keys.includes("manual_lock_update_required") ||
|
|
!blocker.blocker_keys.includes("host_runtime_requirements_missing")
|
|
) {
|
|
throw new Error(`${boundaryClass}: opt-in gate must remain blocked`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyExpectedFileError(fixtureName, output, expectedText) {
|
|
if (!output.includes("file_open=0")) {
|
|
throw new Error(`${fixtureName}: missing file_open=0`);
|
|
}
|
|
|
|
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 (expectedLine.startsWith("error_text=")) {
|
|
const message = expectedLine.slice("error_text=".length);
|
|
if (!output.includes(`file_error_text=${message}`)) {
|
|
throw new Error(`${fixtureName}: missing file error ${message}`);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (expectedLine.startsWith("canon_event=") && !output.includes(expectedLine)) {
|
|
throw new Error(`${fixtureName}: missing ${expectedLine}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyBrowserIniContextStagingPlan() {
|
|
const plan = planIniFileContextStaging({
|
|
manifestText: [
|
|
"tests/browser-staging/example/test.ini",
|
|
"tests/browser-staging/example/test.ngc",
|
|
"tests/browser-staging/example/test.tbl",
|
|
"tests/browser-staging/example/startup.var",
|
|
"tests/browser-staging/example/subs/helper.ngc",
|
|
"tests/browser-staging/example/subs/rm145.ngc",
|
|
"tests/browser-staging/example/more-subs/other.ngc",
|
|
"tests/browser-staging/example/M145",
|
|
].join("\n"),
|
|
sourceRootRel: "tests/browser-staging/example",
|
|
iniFile: "test.ini",
|
|
iniText: [
|
|
"[DISPLAY]",
|
|
"OPEN_FILE = test.ngc",
|
|
"[RS274NGC]",
|
|
"PARAMETER_FILE = startup.var",
|
|
"SUBROUTINE_PATH = subs:more-subs",
|
|
"USER_M_PATH = .",
|
|
"REMAP = M145 modalgroup=10 ngc=rm145",
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = test.tbl",
|
|
].join("\n"),
|
|
wasmDir: "/work/browser-staging/example",
|
|
});
|
|
const actual = plan.files
|
|
.map((file) => [file.sourceRel, file.wasmPath, file.executable])
|
|
.sort((a, b) => a[0].localeCompare(b[0]));
|
|
const expected = [
|
|
["tests/browser-staging/example/M145", "/work/browser-staging/example/M145", true],
|
|
[
|
|
"tests/browser-staging/example/more-subs/other.ngc",
|
|
"/work/browser-staging/example/more-subs/other.ngc",
|
|
false,
|
|
],
|
|
[
|
|
"tests/browser-staging/example/startup.var",
|
|
"/work/browser-staging/example/startup.var",
|
|
false,
|
|
],
|
|
[
|
|
"tests/browser-staging/example/subs/helper.ngc",
|
|
"/work/browser-staging/example/subs/helper.ngc",
|
|
false,
|
|
],
|
|
[
|
|
"tests/browser-staging/example/subs/rm145.ngc",
|
|
"/work/browser-staging/example/subs/rm145.ngc",
|
|
false,
|
|
],
|
|
["tests/browser-staging/example/test.ini", "/work/browser-staging/example/test.ini", false],
|
|
["tests/browser-staging/example/test.ngc", "/work/browser-staging/example/test.ngc", false],
|
|
["tests/browser-staging/example/test.tbl", "/work/browser-staging/example/test.tbl", false],
|
|
];
|
|
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
throw new Error(`browser_ini_context_staging_plan: ${JSON.stringify(actual)}`);
|
|
}
|
|
}
|
|
|
|
async function writeFetchedTextFile(interp, sourcePath, wasmPath, options = {}) {
|
|
interp.writeTextFile(wasmPath, await fetchText(sourcePath));
|
|
if (options.executable) {
|
|
interp.module.FS.chmod(wasmPath, 0o755);
|
|
}
|
|
}
|
|
|
|
async function writeLocalGcodeFixture(interp, fixtureName, wasmPath, expectedWasmPath) {
|
|
if (wasmPath !== expectedWasmPath) {
|
|
throw new Error(`browser_${fixtureName}_local_gcode_staging: path drift`);
|
|
}
|
|
interp.writeTextFile(wasmPath, await fetchText(`../fixtures/gcode/${fixtureName}.ngc`));
|
|
return wasmPath;
|
|
}
|
|
|
|
async function loadSimMachineFiles(machineRel, iniFile, programFile = null) {
|
|
const iniText = await fetchText(
|
|
`../../vendor/linuxcnc/configs/sim/${machineRel}/${iniFile}`,
|
|
);
|
|
const plan = planSimConfigStaging({
|
|
manifestText: await fetchText("../../tools/source-manifest.txt"),
|
|
machineRel,
|
|
iniFile,
|
|
iniText,
|
|
programFile,
|
|
wasmDir: `/work/browser-sim/${machineRel}`,
|
|
});
|
|
|
|
return {
|
|
...plan,
|
|
files: await Promise.all(plan.files.map(async (file) => ({
|
|
path: file.path,
|
|
text: await fetchText(`../../vendor/linuxcnc/${file.sourceRel}`),
|
|
executable: file.executable,
|
|
}))),
|
|
};
|
|
}
|
|
|
|
async function verifySimBoundaryDeclaration(name, options) {
|
|
const {
|
|
machineRel,
|
|
iniFile,
|
|
executionFiles = [],
|
|
expectedDependencies,
|
|
expectedProcesses,
|
|
expectedPythonUiReferences = [],
|
|
expectedPythonRemapReferences = [],
|
|
expectedCoverage,
|
|
fullProcessCoverage = false,
|
|
} = options;
|
|
const boundary = analyzeIniRuntimeBoundaries({
|
|
manifestText: await getSourceManifestText(),
|
|
sourceRootRel: `configs/sim/${machineRel}`,
|
|
sourceSearchRootRel: "configs/sim",
|
|
iniFile,
|
|
iniText: await fetchText(`../../vendor/linuxcnc/configs/sim/${machineRel}/${iniFile}`),
|
|
executionTexts: await Promise.all(executionFiles.map((file) =>
|
|
fetchText(`../../vendor/linuxcnc/configs/sim/${machineRel}/${file}`),
|
|
)),
|
|
});
|
|
|
|
if (boundary.recommendedBlockedKind !== "-") {
|
|
throw new Error(`${name}: unexpected hard block ${boundary.recommendedBlockedKind}`);
|
|
}
|
|
if (!["file", "remap"].includes(expectedCoverage)) {
|
|
throw new Error(`${name}: representative coverage must be file or remap`);
|
|
}
|
|
if (fullProcessCoverage) {
|
|
throw new Error(`${name}: browser sim representatives must not claim full-process coverage`);
|
|
}
|
|
const actualDependencies = boundary.dependencies.join(",");
|
|
const expectedDependencyText = expectedDependencies.join(",");
|
|
if (actualDependencies !== expectedDependencyText) {
|
|
throw new Error(`${name}: dependency drift ${actualDependencies}`);
|
|
}
|
|
for (const [field, expected] of Object.entries(expectedProcesses)) {
|
|
if (boundary[field].requiresProcess !== expected) {
|
|
throw new Error(`${name}: ${field}.requiresProcess drift`);
|
|
}
|
|
}
|
|
const actualPythonUiReferences = boundary.pythonRuntime.uiReferences.join(",");
|
|
const expectedPythonUiReferenceText = expectedPythonUiReferences.join(",");
|
|
if (actualPythonUiReferences !== expectedPythonUiReferenceText) {
|
|
throw new Error(`${name}: Python UI/DB reference drift ${actualPythonUiReferences}`);
|
|
}
|
|
const actualPythonRemapReferences = boundary.pythonRuntime.remapReferences.join(",");
|
|
const expectedPythonRemapReferenceText = expectedPythonRemapReferences.join(",");
|
|
if (actualPythonRemapReferences !== expectedPythonRemapReferenceText) {
|
|
throw new Error(`${name}: Python remap reference drift ${actualPythonRemapReferences}`);
|
|
}
|
|
}
|
|
|
|
async function verifySimHardBlockedBoundary(name, options) {
|
|
const {
|
|
machineRel,
|
|
iniFile,
|
|
executionFiles,
|
|
expectedBlockedKind,
|
|
expectedDependencies,
|
|
expectedProcesses,
|
|
expectedUserMCodes = [],
|
|
expectedUnstagedUserMCodes = expectedUserMCodes,
|
|
expectedPythonUiReferences = [],
|
|
expectedPythonRemapReferences = [],
|
|
expectedVendoredUserMCount = 0,
|
|
expectedRequiresExternalUserM = false,
|
|
expectedToolDatabaseProgram = "",
|
|
} = options;
|
|
const boundary = analyzeIniRuntimeBoundaries({
|
|
manifestText: await getSourceManifestText(),
|
|
sourceRootRel: `configs/sim/${machineRel}`,
|
|
sourceSearchRootRel: "configs/sim",
|
|
iniFile,
|
|
iniText: await fetchText(`../../vendor/linuxcnc/configs/sim/${machineRel}/${iniFile}`),
|
|
executionTexts: await Promise.all(executionFiles.map((file) =>
|
|
fetchText(`../../vendor/linuxcnc/configs/sim/${machineRel}/${file}`),
|
|
)),
|
|
});
|
|
|
|
if (boundary.recommendedBlockedKind !== expectedBlockedKind) {
|
|
throw new Error(`${name}: blocked kind drift ${boundary.recommendedBlockedKind}`);
|
|
}
|
|
const actualDependencies = boundary.dependencies.join(",");
|
|
const expectedDependencyText = expectedDependencies.join(",");
|
|
if (actualDependencies !== expectedDependencyText) {
|
|
throw new Error(`${name}: dependency drift ${actualDependencies}`);
|
|
}
|
|
const actualUserMCodes = boundary.userMRuntime.executionCodes.join(",");
|
|
const expectedUserMCodeText = expectedUserMCodes.join(",");
|
|
if (actualUserMCodes !== expectedUserMCodeText) {
|
|
throw new Error(`${name}: user-M execution code drift ${actualUserMCodes}`);
|
|
}
|
|
const actualUnstagedUserMCodes = boundary.userMRuntime.unstagedExecutionCodes.join(",");
|
|
const expectedUnstagedUserMCodeText = expectedUnstagedUserMCodes.join(",");
|
|
if (actualUnstagedUserMCodes !== expectedUnstagedUserMCodeText) {
|
|
throw new Error(`${name}: unstaged user-M execution code drift ${actualUnstagedUserMCodes}`);
|
|
}
|
|
if (boundary.userMRuntime.vendoredExecutableCount !== expectedVendoredUserMCount) {
|
|
throw new Error(`${name}: vendored user-M count drift`);
|
|
}
|
|
if (boundary.userMRuntime.requiresExternalProcess !== expectedRequiresExternalUserM) {
|
|
throw new Error(`${name}: external user-M process requirement drift`);
|
|
}
|
|
if (boundary.toolDatabaseProgram !== expectedToolDatabaseProgram) {
|
|
throw new Error(`${name}: DB_PROGRAM drift ${boundary.toolDatabaseProgram}`);
|
|
}
|
|
for (const [field, expected] of Object.entries(expectedProcesses)) {
|
|
if (boundary[field].requiresProcess !== expected) {
|
|
throw new Error(`${name}: ${field}.requiresProcess drift`);
|
|
}
|
|
}
|
|
const actualPythonUiReferences = boundary.pythonRuntime.uiReferences.join(",");
|
|
const expectedPythonUiReferenceText = expectedPythonUiReferences.join(",");
|
|
if (actualPythonUiReferences !== expectedPythonUiReferenceText) {
|
|
throw new Error(`${name}: Python UI/DB reference drift ${actualPythonUiReferences}`);
|
|
}
|
|
const actualPythonRemapReferences = boundary.pythonRuntime.remapReferences.join(",");
|
|
const expectedPythonRemapReferenceText = expectedPythonRemapReferences.join(",");
|
|
if (actualPythonRemapReferences !== expectedPythonRemapReferenceText) {
|
|
throw new Error(`${name}: Python remap reference drift ${actualPythonRemapReferences}`);
|
|
}
|
|
}
|
|
|
|
function wrapSimProgramFiles(machine, wrappedName) {
|
|
const sourceFile = machine.files.find((file) => file.path === machine.programPath);
|
|
if (!sourceFile) {
|
|
throw new Error(`${machine.programPath}: wrapped source file`);
|
|
}
|
|
return {
|
|
...machine,
|
|
programPath: `${machine.wasmDir}/${wrappedName}`,
|
|
files: [
|
|
...machine.files,
|
|
{
|
|
path: `${machine.wasmDir}/${wrappedName}`,
|
|
text: `${sourceFile.text}\nM2\n`,
|
|
executable: false,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
let sourceManifestText = null;
|
|
|
|
async function getSourceManifestText() {
|
|
sourceManifestText ??= await fetchText("../../tools/source-manifest.txt");
|
|
return sourceManifestText;
|
|
}
|
|
|
|
async function stageInterpIniContext(interp, name, programFile = "test.ngc") {
|
|
const sourceRootRel = `tests/interp/${name}`;
|
|
const iniText = await fetchText(
|
|
`../../vendor/linuxcnc/${sourceRootRel}/test.ini`,
|
|
);
|
|
const plan = planIniFileContextStaging({
|
|
manifestText: await getSourceManifestText(),
|
|
sourceRootRel,
|
|
iniFile: "test.ini",
|
|
iniText,
|
|
programFile,
|
|
wasmDir: `/work/browser-interp/${name}`,
|
|
});
|
|
|
|
for (const file of plan.files) {
|
|
await writeFetchedTextFile(
|
|
interp,
|
|
`../../vendor/linuxcnc/${file.sourceRel}`,
|
|
file.wasmPath,
|
|
{ executable: file.executable },
|
|
);
|
|
}
|
|
|
|
return plan;
|
|
}
|
|
|
|
function assertFiveAxisMachineFilesStaging(name, files, wasmDir, expectedWasmDir) {
|
|
if (wasmDir !== expectedWasmDir) {
|
|
throw new Error(`browser_fiveaxis_${name}_staging_root: path drift`);
|
|
}
|
|
if (files.length === 0 || new Set(files).size !== files.length) {
|
|
throw new Error(`browser_fiveaxis_${name}_staging_manifest: list drift`);
|
|
}
|
|
for (const file of files) {
|
|
if (`${wasmDir}/${file}` !== `${expectedWasmDir}/${file}`) {
|
|
throw new Error(`browser_fiveaxis_${name}_${file}_staging_manifest: path drift`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function writeFiveAxisMachineFiles(interp, name, sourceDir, wasmDir, expectedWasmDir, files) {
|
|
assertFiveAxisMachineFilesStaging(name, files, wasmDir, expectedWasmDir);
|
|
for (const file of files) {
|
|
await writeFetchedTextFile(interp, `${sourceDir}/${file}`, `${wasmDir}/${file}`);
|
|
}
|
|
return wasmDir;
|
|
}
|
|
|
|
async function writeFiveAxisTrtMachineFiles(interp) {
|
|
const sourceDir =
|
|
"../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting";
|
|
const wasmDir = "/work/browser-fiveaxis/table-rotary-tilting";
|
|
const files = [
|
|
"xyzac-trt.ini",
|
|
"xyzbc-trt.ini",
|
|
"xyzac-trt.tbl",
|
|
"xyzbc-trt.tbl",
|
|
"remap_subs/428remap.ngc",
|
|
"remap_subs/429remap.ngc",
|
|
"remap_subs/430remap.ngc",
|
|
"remap_subs/centering.ngc",
|
|
"remap_subs/helix_ac.ngc",
|
|
"remap_subs/helix_bc.ngc",
|
|
"remap_subs/xyzac_switchkins_sub.ngc",
|
|
"remap_subs/xyzbc_switchkins_sub.ngc",
|
|
"demos/xyzac_switchkins.ngc",
|
|
"demos/xyzbc_switchkins.ngc",
|
|
"demos/xyzac_switchkins_test_1.ngc",
|
|
"demos/xyzac_switchkins_test_2.ngc",
|
|
"demos/xyzac_switchkins_test_3.ngc",
|
|
"demos/boat-xyzac.ngc",
|
|
"demos/boat-xyzbc.ngc",
|
|
"demos/impeller-7bl-xyzac.ngc",
|
|
];
|
|
|
|
return writeFiveAxisMachineFiles(
|
|
interp,
|
|
"table-rotary-tilting",
|
|
sourceDir,
|
|
wasmDir,
|
|
"/work/browser-fiveaxis/table-rotary-tilting",
|
|
files,
|
|
);
|
|
}
|
|
|
|
async function writeFiveAxisTdrMachineFiles(interp) {
|
|
const sourceDir =
|
|
"../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary";
|
|
const wasmDir = "/work/browser-fiveaxis/table-dual-rotary";
|
|
const files = [
|
|
"xyzab-tdr.ini",
|
|
"xyzab-tdr.tbl",
|
|
"remap_subs/428remap.ngc",
|
|
"remap_subs/429remap.ngc",
|
|
"demos/xyzab-tdr-demo.ngc",
|
|
];
|
|
|
|
return writeFiveAxisMachineFiles(
|
|
interp,
|
|
"table-dual-rotary",
|
|
sourceDir,
|
|
wasmDir,
|
|
"/work/browser-fiveaxis/table-dual-rotary",
|
|
files,
|
|
);
|
|
}
|
|
|
|
async function writeFiveAxisBridgeMillMachineFiles(interp) {
|
|
const sourceDir =
|
|
"../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/bridgemill";
|
|
const wasmDir = "/work/browser-fiveaxis/bridgemill";
|
|
const files = [
|
|
"5axis.ini",
|
|
"5axis.tbl",
|
|
"remap_subs/428remap.ngc",
|
|
"remap_subs/429remap.ngc",
|
|
"remap_subs/430remap.ngc",
|
|
"5axisgui.ngc",
|
|
];
|
|
|
|
return writeFiveAxisMachineFiles(
|
|
interp,
|
|
"bridgemill",
|
|
sourceDir,
|
|
wasmDir,
|
|
"/work/browser-fiveaxis/bridgemill",
|
|
files,
|
|
);
|
|
}
|
|
|
|
async function writeRemapRegressionFiles(interp, name, files) {
|
|
const sourceDir = `../../vendor/linuxcnc/tests/remap/${name}`;
|
|
const wasmDir = `/work/browser-remap/${name}`;
|
|
|
|
for (const filename of files) {
|
|
await writeFetchedTextFile(interp, `${sourceDir}/${filename}`, `${wasmDir}/${filename}`);
|
|
}
|
|
|
|
return wasmDir;
|
|
}
|
|
|
|
function assertRemapRegressionFilesStaging(name, files, wasmDir) {
|
|
if (files.length === 0 || new Set(files).size !== files.length) {
|
|
throw new Error(`browser_remap_${name}_fixture_coverage: list drift`);
|
|
}
|
|
for (const file of files) {
|
|
if (`${wasmDir}/${file}` !== `/work/browser-remap/${name}/${file}`) {
|
|
throw new Error(`browser_remap_${name}_${file}_staging_manifest: list drift`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function assertRemapRegressionGeneratedFileStaging(name, filename, wasmPath) {
|
|
if (wasmPath !== `/work/browser-remap/${name}/${filename}`) {
|
|
throw new Error(`browser_remap_${name}_${filename}_generated_staging: path drift`);
|
|
}
|
|
}
|
|
|
|
function assertRemapRegressionUnstagedOverlap(name, files, expectedUnstagedFiles) {
|
|
if (expectedUnstagedFiles.some((file) => files.includes(file))) {
|
|
throw new Error(`browser_remap_${name}_unstaged_fixture_overlap: list drift`);
|
|
}
|
|
}
|
|
|
|
async function writeInterpRegressionFile(interp, name, filename) {
|
|
const sourceDir = `../../vendor/linuxcnc/tests/interp/${name}`;
|
|
const wasmDir = `/work/browser-interp/${name}`;
|
|
|
|
await writeFetchedTextFile(interp, `${sourceDir}/${filename}`, `${wasmDir}/${filename}`);
|
|
|
|
return `${wasmDir}/${filename}`;
|
|
}
|
|
|
|
function assertInterpRegressionFileStaging(name, filename, wasmPath) {
|
|
if (wasmPath !== `/work/browser-interp/${name}/${filename}`) {
|
|
throw new Error(`browser_interp_${name}_${filename}_staging_manifest: list drift`);
|
|
}
|
|
}
|
|
|
|
function assertInterpRegressionFilesStaging(name, files, wasmDir) {
|
|
if (files.length === 0 || new Set(files).size !== files.length) {
|
|
throw new Error(`browser_interp_${name}_fixture_coverage: list drift`);
|
|
}
|
|
for (const file of files) {
|
|
assertInterpRegressionFileStaging(name, file, `${wasmDir}/${file}`);
|
|
}
|
|
}
|
|
|
|
function stripInterpIniContextStagingPrefix(name, wasmPath) {
|
|
const prefix = `/work/browser-interp/${name}/`;
|
|
if (!wasmPath.startsWith(prefix)) {
|
|
throw new Error(`browser_interp_${name}_staging_prefix: path drift ${wasmPath}`);
|
|
}
|
|
return wasmPath.slice(prefix.length);
|
|
}
|
|
|
|
function assertInterpIniContextNgcStaging(name, files, plan) {
|
|
const stagedNgcFiles = plan.files
|
|
.filter((file) => file.sourceRel.endsWith(".ngc"))
|
|
.map((file) => stripInterpIniContextStagingPrefix(name, file.wasmPath))
|
|
.sort();
|
|
if (stagedNgcFiles.join(",") !== files.join(",")) {
|
|
throw new Error(`browser_interp_${name}_staging_manifest: list drift`);
|
|
}
|
|
}
|
|
|
|
async function writeInterpRegressionFiles(interp, name, files) {
|
|
const sourceDir = `../../vendor/linuxcnc/tests/interp/${name}`;
|
|
const wasmDir = `/work/browser-interp/${name}`;
|
|
|
|
for (const filename of files) {
|
|
await writeFetchedTextFile(interp, `${sourceDir}/${filename}`, `${wasmDir}/${filename}`);
|
|
}
|
|
|
|
return wasmDir;
|
|
}
|
|
|
|
async function writeCcompRegressionFiles(interp, name) {
|
|
const sourceDir = `../../vendor/linuxcnc/tests/ccomp/${name}`;
|
|
const wasmDir = `/work/browser-ccomp/${name}`;
|
|
|
|
await writeFetchedTextFile(interp, `${sourceDir}/test.ngc`, `${wasmDir}/test.ngc`);
|
|
await writeFetchedTextFile(interp, `${sourceDir}/test.tbl`, `${wasmDir}/test.tbl`);
|
|
interp.writeTextFile(
|
|
`${wasmDir}/test.ini`,
|
|
[
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = test.tbl",
|
|
"[TRAJ]",
|
|
"COORDINATES = X Y Z A B C U V W",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
|
|
return wasmDir;
|
|
}
|
|
|
|
const ccompRegressionFiles = [
|
|
"test.ngc",
|
|
"test.tbl",
|
|
];
|
|
|
|
function assertCcompRegressionFilesStaging(name, wasmDir) {
|
|
if (ccompRegressionFiles.length !== 2 || new Set(ccompRegressionFiles).size !== ccompRegressionFiles.length) {
|
|
throw new Error("browser_interp_ccomp_fixture_coverage: list drift");
|
|
}
|
|
for (const file of ccompRegressionFiles) {
|
|
if (`${wasmDir}/${file}` !== `/work/browser-ccomp/${name}/${file}`) {
|
|
throw new Error(`browser_interp_ccomp_${name}_${file}_staging_manifest: list drift`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function writeG10RegressionFiles(interp, name, files) {
|
|
const sourceDir = `../../vendor/linuxcnc/tests/interp/g10/${name}`;
|
|
const wasmDir = `/work/browser-interp/g10/${name}`;
|
|
|
|
for (const filename of files) {
|
|
await writeFetchedTextFile(interp, `${sourceDir}/${filename}`, `${wasmDir}/${filename}`);
|
|
}
|
|
|
|
if (files.includes("test.tbl")) {
|
|
interp.writeTextFile(
|
|
`${wasmDir}/test.ini`,
|
|
[
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = test.tbl",
|
|
"[TRAJ]",
|
|
"COORDINATES = X Y Z A B C U V W",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
return wasmDir;
|
|
}
|
|
|
|
function assertG10RegressionFilesStaging(name, files, wasmDir) {
|
|
if (files.length === 0 || new Set(files).size !== files.length) {
|
|
throw new Error(`browser_interp_g10_${name}_fixture_coverage: list drift`);
|
|
}
|
|
for (const file of files) {
|
|
if (`${wasmDir}/${file}` !== `/work/browser-interp/g10/${name}/${file}`) {
|
|
throw new Error(`browser_interp_g10_${name}_${file}_staging_manifest: list drift`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function assertVendorNcFileStaging(filename, wasmPath, expectedRoot) {
|
|
if (wasmPath !== `${expectedRoot}/${filename}`) {
|
|
throw new Error(`browser_nc_files_${filename}_staging_manifest: path drift`);
|
|
}
|
|
}
|
|
|
|
function assertVendorNcFileContextStaging(filename, wasmDir, files) {
|
|
const expectedRoot = "/work/browser-nc-files-context";
|
|
const expectedFiles = [filename, "tool.tbl", "nc_files_context.ini"];
|
|
if (wasmDir !== expectedRoot) {
|
|
throw new Error(`browser_nc_files_${filename}_context_root: path drift`);
|
|
}
|
|
if (
|
|
files.length !== expectedFiles.length ||
|
|
new Set(files).size !== files.length ||
|
|
files.join(",") !== expectedFiles.join(",")
|
|
) {
|
|
throw new Error(`browser_nc_files_${filename}_context_manifest: list drift`);
|
|
}
|
|
for (const file of files) {
|
|
if (`${wasmDir}/${file}` !== `${expectedRoot}/${file}`) {
|
|
throw new Error(`browser_nc_files_${filename}_${file}_context_staging: path drift`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function runVendorNcFile(interp, filename) {
|
|
const wasmPath = `/work/browser-nc-files/${filename}`;
|
|
assertVendorNcFileStaging(filename, wasmPath, "/work/browser-nc-files");
|
|
await writeFetchedTextFile(
|
|
interp,
|
|
`../../vendor/linuxcnc/nc_files/${filename}`,
|
|
wasmPath,
|
|
);
|
|
return interp.runFile(wasmPath);
|
|
}
|
|
|
|
async function runVendorNcFileWithToolTable(interp, filename) {
|
|
const wasmDir = "/work/browser-nc-files-context";
|
|
const files = [filename, "tool.tbl", "nc_files_context.ini"];
|
|
const wasmPath = `${wasmDir}/${filename}`;
|
|
const iniPath = `${wasmDir}/nc_files_context.ini`;
|
|
assertVendorNcFileContextStaging(filename, wasmDir, files);
|
|
await writeFetchedTextFile(
|
|
interp,
|
|
`../../vendor/linuxcnc/nc_files/${filename}`,
|
|
wasmPath,
|
|
);
|
|
interp.writeTextFile(
|
|
`${wasmDir}/tool.tbl`,
|
|
"T1 P1 D10.000000 Z+0.000000 ; nc_files representative context tool\n",
|
|
);
|
|
interp.writeTextFile(
|
|
iniPath,
|
|
[
|
|
"[RS274NGC]",
|
|
"SUBROUTINE_PATH = .",
|
|
"",
|
|
"[TRAJ]",
|
|
"COORDINATES = X Y Z A B C U V W",
|
|
"",
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = tool.tbl",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
return interp.runFileWithIni(wasmPath, iniPath);
|
|
}
|
|
|
|
async function verifyRejects(fixtureName, operation, expectedPattern) {
|
|
try {
|
|
await operation();
|
|
} catch (error) {
|
|
if (!expectedPattern.test(error.message)) {
|
|
throw new Error(`${fixtureName}: unexpected error ${error.message}`);
|
|
}
|
|
return;
|
|
}
|
|
throw new Error(`${fixtureName}: expected rejection`);
|
|
}
|
|
|
|
const interpPrints = [];
|
|
function runWithCapturedPrints(operation) {
|
|
interpPrints.length = 0;
|
|
const output = operation();
|
|
return { output, prints: interpPrints.join("\n") };
|
|
}
|
|
|
|
try {
|
|
verifyBrowserIniContextStagingPlan();
|
|
const boundaryPhaseCompletionRows =
|
|
await verifyBrowserBoundaryPhaseCompletion();
|
|
const generatedArtifactDocumentationCoverage =
|
|
await verifyBrowserGeneratedArtifactDocumentationCoverage();
|
|
await verifyBrowserIniBoundarySummary();
|
|
const nextBoundaryWorklistRows =
|
|
await verifyBrowserNextBoundaryWorklist();
|
|
const boundaryProofGateRows =
|
|
await verifyBrowserBoundaryProofGates();
|
|
await verifyBrowserBoundarySummary();
|
|
const userMTransitionPlanRows =
|
|
await verifyBrowserUserMTransitionPlan();
|
|
await verifyBrowserUserMNativeTransitionAlignment();
|
|
const userMNativeRuntimeStatePlanRows =
|
|
await verifyBrowserUserMNativeRuntimeStatePlan();
|
|
const userMNativeRuntimeReadinessRows =
|
|
await verifyBrowserUserMNativeRuntimeReadiness();
|
|
const userMNativeRuntimeProbeGateRows =
|
|
await verifyBrowserUserMNativeRuntimeProbeGate();
|
|
const toolDbTransactionPlanRows =
|
|
await verifyBrowserToolDbTransactionPlan();
|
|
const toolDbNativeRuntimeReadinessRows =
|
|
await verifyBrowserToolDbNativeRuntimeReadiness();
|
|
const toolDbNativeRuntimeProbeGateRows =
|
|
await verifyBrowserToolDbNativeRuntimeProbeGate();
|
|
const pythonRuntimeContractRows =
|
|
await verifyBrowserPythonRuntimeContract();
|
|
const pythonNativeRuntimeReadinessRows =
|
|
await verifyBrowserPythonNativeRuntimeReadiness();
|
|
const pythonNativeRuntimeProbeGateRows =
|
|
await verifyBrowserPythonNativeRuntimeProbeGate();
|
|
const pythonNativeRuntimeStatePlanRows =
|
|
await verifyBrowserPythonNativeRuntimeStatePlan();
|
|
const pythonNativeRuntimeFixturePlanRows =
|
|
await verifyBrowserPythonNativeRuntimeFixturePlan();
|
|
const nativeProofAlignmentRows = await verifyBrowserNativeProofAlignment();
|
|
const runtimeNativeAlignmentSummaryRows =
|
|
await verifyBrowserRuntimeNativeAlignmentSummary();
|
|
const nativeRuntimeProbeSummaryRows =
|
|
await verifyBrowserNativeRuntimeProbeSummary();
|
|
const runtimeProbeGateAlignmentRows =
|
|
await verifyBrowserRuntimeProbeGateAlignment();
|
|
verifyBrowserNativeSourceProofRuntimeProbeParity(
|
|
nativeProofAlignmentRows,
|
|
nativeRuntimeProbeSummaryRows,
|
|
runtimeProbeGateAlignmentRows,
|
|
);
|
|
const nativeRuntimeProbeExecutionPlanRows =
|
|
await verifyBrowserNativeRuntimeProbeExecutionPlan();
|
|
const nativeRuntimeProbePassEvidenceRows =
|
|
await verifyBrowserNativeRuntimeProbePassEvidenceContract();
|
|
const runtimeBoundaryContractSummaryRows =
|
|
await verifyBrowserRuntimeBoundaryContractSummary();
|
|
verifyBrowserRuntimeContractNativeAlignmentParity(
|
|
runtimeBoundaryContractSummaryRows,
|
|
runtimeNativeAlignmentSummaryRows,
|
|
);
|
|
verifyBrowserRuntimeFamilyContractAlignmentParity({
|
|
userMTransitionRows: userMTransitionPlanRows,
|
|
userMRuntimeStatePlanRows: userMNativeRuntimeStatePlanRows,
|
|
userMRuntimeReadinessRows: userMNativeRuntimeReadinessRows,
|
|
userMRuntimeProbeGateRows: userMNativeRuntimeProbeGateRows,
|
|
toolDbTransactionRows: toolDbTransactionPlanRows,
|
|
toolDbRuntimeReadinessRows: toolDbNativeRuntimeReadinessRows,
|
|
toolDbRuntimeProbeGateRows: toolDbNativeRuntimeProbeGateRows,
|
|
pythonRuntimeContractRows,
|
|
pythonRuntimeReadinessRows: pythonNativeRuntimeReadinessRows,
|
|
pythonRuntimeProbeGateRows: pythonNativeRuntimeProbeGateRows,
|
|
pythonRuntimeStatePlanRows: pythonNativeRuntimeStatePlanRows,
|
|
pythonRuntimeFixturePlanRows: pythonNativeRuntimeFixturePlanRows,
|
|
runtimeContractSummaryRows: runtimeBoundaryContractSummaryRows,
|
|
});
|
|
await verifyBrowserRuntimeNativeAlignmentDetails(runtimeNativeAlignmentSummaryRows);
|
|
const blockedRuntimePromotionLockRows =
|
|
await verifyBrowserBlockedRuntimePromotionLock();
|
|
verifyBrowserNextBoundaryWorklistProofGateParity({
|
|
worklistRows: nextBoundaryWorklistRows,
|
|
proofGateRows: boundaryProofGateRows,
|
|
promotionLockRows: blockedRuntimePromotionLockRows,
|
|
});
|
|
const runtimeBoundaryPromotionReadinessRows =
|
|
await verifyBrowserRuntimeBoundaryPromotionReadiness();
|
|
const runtimeBoundaryPromotionBlockerRows =
|
|
await verifyBrowserRuntimeBoundaryPromotionBlockers();
|
|
const runtimeBoundaryPostNativePassGateRows =
|
|
await verifyBrowserRuntimeBoundaryPostNativePassGates();
|
|
const runtimeBoundaryHostPreflightRows =
|
|
await verifyBrowserRuntimeBoundaryHostPreflight();
|
|
const runtimeBoundaryHostRequirementSummaryRows =
|
|
await verifyBrowserRuntimeBoundaryHostRequirementSummary();
|
|
const runtimeBoundaryHostUnblockPlanRows =
|
|
await verifyBrowserRuntimeBoundaryHostUnblockPlan();
|
|
const runtimeBoundaryFamilyHostReadinessRows =
|
|
await verifyBrowserRuntimeBoundaryFamilyHostReadiness();
|
|
verifyBrowserBlockedRuntimeHostRequirementParity({
|
|
hostPreflightRows: runtimeBoundaryHostPreflightRows,
|
|
requirementSummaryRows: runtimeBoundaryHostRequirementSummaryRows,
|
|
unblockPlanRows: runtimeBoundaryHostUnblockPlanRows,
|
|
familyHostReadinessRows: runtimeBoundaryFamilyHostReadinessRows,
|
|
});
|
|
const runtimeBoundaryHostReadinessRollupRows =
|
|
await verifyBrowserRuntimeBoundaryHostReadinessRollup();
|
|
const runtimeBoundaryOptInProbeDispatchPlanRows =
|
|
await verifyBrowserRuntimeBoundaryOptInProbeDispatchPlan();
|
|
const runtimeBoundaryOptInProbeDispatchRollupRows =
|
|
await verifyBrowserRuntimeBoundaryOptInProbeDispatchRollup();
|
|
const runtimeBoundaryOptInProbeSkipEvidenceRows =
|
|
await verifyBrowserRuntimeBoundaryOptInProbeSkipEvidenceContract();
|
|
verifyBrowserBlockedRuntimeProbeExecutionParity({
|
|
executionPlanRows: nativeRuntimeProbeExecutionPlanRows,
|
|
passEvidenceRows: nativeRuntimeProbePassEvidenceRows,
|
|
hostPreflightRows: runtimeBoundaryHostPreflightRows,
|
|
dispatchPlanRows: runtimeBoundaryOptInProbeDispatchPlanRows,
|
|
skipEvidenceRows: runtimeBoundaryOptInProbeSkipEvidenceRows,
|
|
});
|
|
const runtimeBoundaryOptInProbeSkipEvidenceRollupRows =
|
|
await verifyBrowserRuntimeBoundaryOptInProbeSkipEvidenceRollup();
|
|
verifyBrowserBlockedRuntimeRollupParity({
|
|
hostReadinessRollupRows: runtimeBoundaryHostReadinessRollupRows,
|
|
dispatchRollupRows: runtimeBoundaryOptInProbeDispatchRollupRows,
|
|
skipEvidenceRollupRows: runtimeBoundaryOptInProbeSkipEvidenceRollupRows,
|
|
familyHostReadinessRows: runtimeBoundaryFamilyHostReadinessRows,
|
|
dispatchPlanRows: runtimeBoundaryOptInProbeDispatchPlanRows,
|
|
skipEvidenceRows: runtimeBoundaryOptInProbeSkipEvidenceRows,
|
|
});
|
|
const runtimeBoundaryNativeEvidenceAcceptanceGateRows =
|
|
await verifyBrowserRuntimeBoundaryNativeEvidenceAcceptanceGate();
|
|
verifyBrowserBlockedRuntimePromotionGateParity({
|
|
promotionLockRows: blockedRuntimePromotionLockRows,
|
|
promotionReadinessRows: runtimeBoundaryPromotionReadinessRows,
|
|
promotionBlockerRows: runtimeBoundaryPromotionBlockerRows,
|
|
postNativePassGateRows: runtimeBoundaryPostNativePassGateRows,
|
|
nativeEvidenceAcceptanceGateRows: runtimeBoundaryNativeEvidenceAcceptanceGateRows,
|
|
passEvidenceRows: nativeRuntimeProbePassEvidenceRows,
|
|
});
|
|
const nextBoundaryRecommendationRows =
|
|
await verifyBrowserNextBoundaryRecommendations();
|
|
verifyBrowserNextBoundaryRecommendationCountParity({
|
|
recommendationRows: nextBoundaryRecommendationRows,
|
|
userMRuntimeProbeGateRows: userMNativeRuntimeProbeGateRows,
|
|
toolDbRuntimeProbeGateRows: toolDbNativeRuntimeProbeGateRows,
|
|
pythonRuntimeContractRows,
|
|
});
|
|
verifyBrowserNextBoundaryRecommendationSourceArtifactCoverage({
|
|
recommendationRows: nextBoundaryRecommendationRows,
|
|
wasmArtifactNames: generatedArtifactDocumentationCoverage.wasmArtifactNames,
|
|
});
|
|
verifyBrowserBlockedRuntimeOptInGateParity({
|
|
recommendationRows: nextBoundaryRecommendationRows,
|
|
executionPlanRows: nativeRuntimeProbeExecutionPlanRows,
|
|
promotionReadinessRows: runtimeBoundaryPromotionReadinessRows,
|
|
promotionBlockerRows: runtimeBoundaryPromotionBlockerRows,
|
|
postNativePassGateRows: runtimeBoundaryPostNativePassGateRows,
|
|
hostPreflightRows: runtimeBoundaryHostPreflightRows,
|
|
familyHostReadinessRows: runtimeBoundaryFamilyHostReadinessRows,
|
|
dispatchPlanRows: runtimeBoundaryOptInProbeDispatchPlanRows,
|
|
skipEvidenceRows: runtimeBoundaryOptInProbeSkipEvidenceRows,
|
|
evidenceAcceptanceGateRows: runtimeBoundaryNativeEvidenceAcceptanceGateRows,
|
|
});
|
|
verifyBrowserBoundaryPhaseCompletionCountParity(
|
|
boundaryPhaseCompletionRows,
|
|
new Map([
|
|
["native_source_proof_alignment", nativeProofAlignmentRows.length],
|
|
["runtime_native_alignment_artifacts", runtimeNativeAlignmentSummaryRows.length],
|
|
["native_runtime_probe_summary", nativeRuntimeProbeSummaryRows.length],
|
|
["runtime_probe_gate_alignment", runtimeProbeGateAlignmentRows.length],
|
|
["blocked_runtime_worklist_recommendation_consistency", nextBoundaryRecommendationRows.length],
|
|
["native_runtime_probe_execution_plan", nativeRuntimeProbeExecutionPlanRows.length],
|
|
["native_runtime_probe_pass_evidence_contract", nativeRuntimeProbePassEvidenceRows.length],
|
|
["blocked_runtime_probe_execution_consistency", nativeRuntimeProbeExecutionPlanRows.length],
|
|
["runtime_boundary_promotion_readiness", runtimeBoundaryPromotionReadinessRows.length],
|
|
["runtime_boundary_promotion_blockers", runtimeBoundaryPromotionBlockerRows.length],
|
|
["runtime_boundary_post_native_pass_gates", runtimeBoundaryPostNativePassGateRows.length],
|
|
["blocked_runtime_promotion_gate_consistency", runtimeBoundaryPromotionReadinessRows.length],
|
|
["runtime_boundary_host_preflight", runtimeBoundaryHostPreflightRows.length],
|
|
["runtime_boundary_host_requirement_summary", runtimeBoundaryHostRequirementSummaryRows.length],
|
|
["runtime_boundary_host_unblock_plan", runtimeBoundaryHostUnblockPlanRows.length],
|
|
["runtime_boundary_family_host_readiness", runtimeBoundaryFamilyHostReadinessRows.length],
|
|
["blocked_runtime_host_requirement_consistency", runtimeBoundaryFamilyHostReadinessRows.length],
|
|
["runtime_boundary_host_readiness_rollup", runtimeBoundaryHostReadinessRollupRows.length],
|
|
["runtime_boundary_opt_in_probe_dispatch_plan", runtimeBoundaryOptInProbeDispatchPlanRows.length],
|
|
["runtime_boundary_opt_in_probe_dispatch_rollup", runtimeBoundaryOptInProbeDispatchRollupRows.length],
|
|
["runtime_boundary_opt_in_probe_skip_evidence_contract", runtimeBoundaryOptInProbeSkipEvidenceRows.length],
|
|
["runtime_boundary_opt_in_probe_skip_evidence_rollup", runtimeBoundaryOptInProbeSkipEvidenceRollupRows.length],
|
|
["blocked_runtime_rollup_consistency", runtimeBoundaryFamilyHostReadinessRows.length],
|
|
["runtime_boundary_native_evidence_acceptance_gate", runtimeBoundaryNativeEvidenceAcceptanceGateRows.length],
|
|
["blocked_runtime_opt_in_gate_consistency", nextBoundaryRecommendationRows.length],
|
|
["native_source_proof_runtime_probe_consistency", nativeProofAlignmentRows.length],
|
|
["user_m_transition_contract", userMTransitionPlanRows.length],
|
|
["user_m_native_runtime_state_plan", userMNativeRuntimeStatePlanRows.length],
|
|
["user_m_native_runtime_readiness", userMNativeRuntimeReadinessRows.length],
|
|
["user_m_native_runtime_probe_gate", userMNativeRuntimeProbeGateRows.length],
|
|
["tool_db_transaction_contract", toolDbTransactionPlanRows.length],
|
|
["tool_db_native_runtime_readiness", toolDbNativeRuntimeReadinessRows.length],
|
|
["tool_db_native_runtime_probe_gate", toolDbNativeRuntimeProbeGateRows.length],
|
|
["python_runtime_contract", pythonRuntimeContractRows.length],
|
|
["python_native_runtime_readiness", pythonNativeRuntimeReadinessRows.length],
|
|
["python_native_runtime_probe_gate", pythonNativeRuntimeProbeGateRows.length],
|
|
["python_native_runtime_state_plan", pythonNativeRuntimeStatePlanRows.length],
|
|
["python_native_runtime_fixture_plan", pythonNativeRuntimeFixturePlanRows.length],
|
|
["runtime_contract_summary", runtimeBoundaryContractSummaryRows.length],
|
|
["runtime_family_contract_alignment_consistency", runtimeBoundaryContractSummaryRows.length],
|
|
[
|
|
"wasm_inventory_artifact_documentation_coverage",
|
|
generatedArtifactDocumentationCoverage.wasmArtifactNames.length,
|
|
],
|
|
[
|
|
"native_artifact_documentation_coverage",
|
|
generatedArtifactDocumentationCoverage.nativeArtifactTokens.length,
|
|
],
|
|
]),
|
|
);
|
|
|
|
const interp = await createLinuxCncInterpSdk({
|
|
locateFile(path) {
|
|
if (path === "linuxcnc_interp.wasm") {
|
|
return "../../build/wasm/core/linuxcnc_interp.wasm";
|
|
}
|
|
return path;
|
|
},
|
|
print(message) {
|
|
interpPrints.push(message);
|
|
},
|
|
printErr(message) {
|
|
console.error(message);
|
|
},
|
|
});
|
|
const ini = await createLinuxCncIniSdk({
|
|
locateFile(path) {
|
|
if (path === "linuxcnc_ini.wasm") {
|
|
return "../../runtime/ui/ini-panel/linuxcnc_ini.wasm";
|
|
}
|
|
return path;
|
|
},
|
|
print() {},
|
|
printErr(message) {
|
|
console.error(message);
|
|
},
|
|
});
|
|
|
|
const namedParamIniPath = "/work/namedparams.ini";
|
|
interp.writeTextFile(namedParamIniPath, await fetchText("../fixtures/ini/namedparams.ini"));
|
|
|
|
verifyExpectedOutput(
|
|
"probe_init_and_synch",
|
|
interp.probeInitAndSynch(),
|
|
[
|
|
"init=0",
|
|
"setup.length_units=2",
|
|
"setup.origin_index=1",
|
|
"setup.distance_mode=0",
|
|
"setup.feed_mode=0",
|
|
"setup.motion_mode=800",
|
|
"canon_event=INIT_CANON",
|
|
"canon_event=USE_LENGTH_UNITS units=2",
|
|
"canon_event=SET_G5X_OFFSET index=1",
|
|
"canon_event=SET_G92_OFFSET x=0 y=0 z=0",
|
|
"canon_event=SET_XY_ROTATION rotation=0",
|
|
"canon_event=SET_FEED_REFERENCE reference=2",
|
|
"inch_init=0",
|
|
"inch_setup.length_units=1",
|
|
"inch_external_length_units=0.0393701",
|
|
"inch_canon_event=USE_LENGTH_UNITS units=1",
|
|
"synch=0",
|
|
"synch.current_pocket=2",
|
|
"synch.selected_pocket=2",
|
|
"synch.tool_0=2",
|
|
"synch.tool_2=2",
|
|
].join("\n"),
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"probe_indexer",
|
|
interp.probeIndexer(),
|
|
[
|
|
"execute=0",
|
|
"post_a=90",
|
|
"canon_event=SET_MOTION_CONTROL_MODE mode=2 tolerance=0",
|
|
"canon_event=UNLOCK_ROTARY line=1 joint=0",
|
|
"canon_event=STRAIGHT_TRAVERSE line=1 x=0 y=0 z=0 a=90 b=0 c=0 u=0 v=0 w=0",
|
|
"canon_event=LOCK_ROTARY line=1 joint=0",
|
|
"canon_event=SET_MOTION_CONTROL_MODE mode=1 tolerance=0",
|
|
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=0",
|
|
"canon_event=UPDATE_TAG line=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"probe_named_parameters",
|
|
interp.probeNamedParameters(namedParamIniPath),
|
|
[
|
|
"init_named_parameters=0",
|
|
"global_named_count=57",
|
|
"_metric_machine: rc=0 found=1 value=1",
|
|
"_motion_mode: rc=0 found=1 value=10",
|
|
"_metric: rc=0 found=1 value=1",
|
|
"_feed: rc=0 found=1 value=123.45",
|
|
"_rpm: rc=0 found=1 value=678.9",
|
|
"_x: rc=0 found=1 value=1.25",
|
|
"_current_tool: rc=0 found=1 value=12",
|
|
"_ini[traj]max_linear_velocity: rc=0 found=1 value=35",
|
|
"_hal[standalone.pin-bit]: rc=0 found=1 value=1",
|
|
"_hal[standalone.signal-float]: rc=0 found=1 value=98.25",
|
|
"_hal[standalone.param-s32]: rc=0 found=1 value=-17",
|
|
"_hal[standalone.pin-u32]: rc=0 found=1 value=1.23457e+08",
|
|
"_hal[standalone.signal-s64]: rc=0 found=1 value=-9e+09",
|
|
"_hal[standalone.param-u64]: rc=0 found=1 value=9e+09",
|
|
"_hal[standalone.disconnected-float]: rc=0 found=1 value=12.5",
|
|
"_hal[standalone.missing]: rc=0 found=0 value=0",
|
|
].join("\n"),
|
|
);
|
|
|
|
for (const fixtureName of INTERP_BROWSER_MDI_FIXTURES) {
|
|
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 INTERP_ERROR_FIXTURES) {
|
|
verifyExpectedOutput(
|
|
errorFixtureName,
|
|
interp.runProgram(await fetchText(`../fixtures/gcode_errors/${errorFixtureName}.ngc`)),
|
|
(await fetchText(`../fixtures/canon_errors/${errorFixtureName}.expected`)).trimEnd(),
|
|
);
|
|
}
|
|
|
|
for (const errorFixtureName of INTERP_ERROR_FIXTURES) {
|
|
const programPath = `/work/${errorFixtureName}-error.ngc`;
|
|
interp.writeTextFile(
|
|
programPath,
|
|
await fetchText(`../fixtures/gcode_errors/${errorFixtureName}.ngc`),
|
|
);
|
|
verifyExpectedFileError(
|
|
`${errorFixtureName}_file`,
|
|
interp.runFile(programPath),
|
|
(await fetchText(`../fixtures/canon_errors/${errorFixtureName}.expected`)).trimEnd(),
|
|
);
|
|
}
|
|
|
|
const namedParamProgramText = await fetchText(
|
|
`../fixtures/gcode/${INTERP_INI_FIXTURE}.ngc`,
|
|
);
|
|
const namedParamExpectedText = (
|
|
await fetchText(`../fixtures/canon/${INTERP_INI_FIXTURE}.events`)
|
|
).trimEnd();
|
|
verifyExpectedOutput(
|
|
INTERP_INI_FIXTURE,
|
|
interp.runProgramWithIni(namedParamProgramText, namedParamIniPath),
|
|
namedParamExpectedText,
|
|
);
|
|
|
|
const baselineIniProgramText = await fetchText(
|
|
`../fixtures/gcode/${INTERP_BASELINE_INI_FIXTURE}.ngc`,
|
|
);
|
|
const baselineIniExpectedText = (
|
|
await fetchText(`../fixtures/canon/${INTERP_BASELINE_INI_FIXTURE}.events`)
|
|
).trimEnd();
|
|
verifyExpectedOutput(
|
|
INTERP_BASELINE_INI_FIXTURE,
|
|
interp.runProgramWithIni(baselineIniProgramText, namedParamIniPath),
|
|
baselineIniExpectedText,
|
|
);
|
|
|
|
const spindleOrientOffsetProgramText = await fetchText(
|
|
`../fixtures/gcode/${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}.ngc`,
|
|
);
|
|
const spindleOrientOffsetExpectedText = (
|
|
await fetchText(`../fixtures/canon/${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}.events`)
|
|
).trimEnd();
|
|
const spindleOrientOffsetIniPath = "/work/browser-spindle-orient-offset.ini";
|
|
interp.writeTextFile(
|
|
spindleOrientOffsetIniPath,
|
|
await fetchText("../fixtures/ini/spindle_orient_offset.ini"),
|
|
);
|
|
const disableFanucStyleSubIniPath = "/work/browser-disable-fanuc-style-sub.ini";
|
|
interp.writeTextFile(
|
|
disableFanucStyleSubIniPath,
|
|
await fetchText("../fixtures/ini/disable_fanuc_style_sub.ini"),
|
|
);
|
|
verifyExpectedOutput(
|
|
INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE,
|
|
interp.runProgramWithIni(spindleOrientOffsetProgramText, spindleOrientOffsetIniPath),
|
|
spindleOrientOffsetExpectedText,
|
|
);
|
|
|
|
const coordinateOffsetsPath = await writeLocalGcodeFixture(
|
|
interp,
|
|
INTERP_COORDINATE_OFFSETS_FILE_FIXTURE,
|
|
`/work/${INTERP_COORDINATE_OFFSETS_FILE_FIXTURE}.ngc`,
|
|
`/work/${INTERP_COORDINATE_OFFSETS_FILE_FIXTURE}.ngc`,
|
|
);
|
|
verifyExpectedOutput(
|
|
`${INTERP_COORDINATE_OFFSETS_FILE_FIXTURE}_file`,
|
|
interp.runFile(coordinateOffsetsPath),
|
|
(
|
|
await fetchText(
|
|
`../fixtures/canon_file/${INTERP_COORDINATE_OFFSETS_FILE_FIXTURE}.events`,
|
|
)
|
|
).trimEnd(),
|
|
);
|
|
|
|
const positionParamsPath = await writeLocalGcodeFixture(
|
|
interp,
|
|
INTERP_POSITION_PARAMS_FILE_FIXTURE,
|
|
`/work/${INTERP_POSITION_PARAMS_FILE_FIXTURE}.ngc`,
|
|
`/work/${INTERP_POSITION_PARAMS_FILE_FIXTURE}.ngc`,
|
|
);
|
|
verifyExpectedOutput(
|
|
`${INTERP_POSITION_PARAMS_FILE_FIXTURE}_file`,
|
|
interp.runFile(positionParamsPath),
|
|
(
|
|
await fetchText(
|
|
`../fixtures/canon_file/${INTERP_POSITION_PARAMS_FILE_FIXTURE}.events`,
|
|
)
|
|
).trimEnd(),
|
|
);
|
|
|
|
for (const fileFixtureName of INTERP_BROWSER_FILE_FIXTURES) {
|
|
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(),
|
|
);
|
|
}
|
|
|
|
verifyExpectedOutput(
|
|
"browser_minimal_linear_run_steps",
|
|
interp.runFile("/work/minimal_linear.ngc"),
|
|
[
|
|
"run_step phase=read step=1 rc=0 line=1",
|
|
"run_step phase=execute step=1 rc=0 line=1 x=1 y=2 z=0",
|
|
"statement_uri=G0%20X1.0%20Y2.0%20%28Comment%29",
|
|
"run_step phase=execute step=2 rc=0 line=2 x=3 y=4 z=0",
|
|
"statement_uri=G1%20X3.0%20Y4.0%20F120.0",
|
|
].join("\n"),
|
|
);
|
|
|
|
const namedParamPath = `/work/${INTERP_INI_FIXTURE}.ngc`;
|
|
interp.writeTextFile(namedParamPath, namedParamProgramText);
|
|
verifyExpectedOutput(
|
|
`${INTERP_INI_FIXTURE}_file`,
|
|
interp.runFileWithIni(namedParamPath, namedParamIniPath),
|
|
namedParamExpectedText,
|
|
);
|
|
|
|
const iniToolTableDir = "/work/browser-ini-tool-table";
|
|
const iniToolTableIniPath = `${iniToolTableDir}/ini_tool_table.ini`;
|
|
const iniToolTableProgramPath = `${iniToolTableDir}/ini_tool_table.ngc`;
|
|
interp.writeTextFile(iniToolTableIniPath, await fetchText("../fixtures/ini/ini_tool_table.ini"));
|
|
interp.writeTextFile(
|
|
`${iniToolTableDir}/ini_tool_table.tbl`,
|
|
await fetchText("../fixtures/ini/ini_tool_table.tbl"),
|
|
);
|
|
interp.writeTextFile(
|
|
iniToolTableProgramPath,
|
|
await fetchText("../fixtures/gcode/ini_tool_table.ngc"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_ini_tool_table_file",
|
|
interp.runFileWithIni(iniToolTableProgramPath, iniToolTableIniPath),
|
|
(await fetchText("../fixtures/canon/ini_tool_table.events")).trimEnd(),
|
|
);
|
|
|
|
const baselineIniPath = `/work/${INTERP_BASELINE_INI_FIXTURE}.ngc`;
|
|
interp.writeTextFile(baselineIniPath, baselineIniProgramText);
|
|
verifyExpectedOutput(
|
|
`${INTERP_BASELINE_INI_FIXTURE}_file`,
|
|
interp.runFileWithIni(baselineIniPath, namedParamIniPath),
|
|
baselineIniExpectedText,
|
|
);
|
|
|
|
const spindleOrientOffsetPath = `/work/${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}.ngc`;
|
|
interp.writeTextFile(spindleOrientOffsetPath, spindleOrientOffsetProgramText);
|
|
verifyExpectedOutput(
|
|
`${INTERP_SPINDLE_ORIENT_OFFSET_FIXTURE}_file`,
|
|
interp.runFileWithIni(spindleOrientOffsetPath, spindleOrientOffsetIniPath),
|
|
spindleOrientOffsetExpectedText,
|
|
);
|
|
|
|
const disableFanucStyleSubPath = "/work/browser-disable_fanuc_style_sub_m98.ngc";
|
|
interp.writeTextFile(
|
|
disableFanucStyleSubPath,
|
|
await fetchText("../fixtures/gcode/disable_fanuc_style_sub_m98.ngc"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_disable_fanuc_style_sub_m98_file",
|
|
interp.runFileWithIni(disableFanucStyleSubPath, disableFanucStyleSubIniPath),
|
|
[
|
|
"file_open=0",
|
|
"file_saw_error=1",
|
|
"file_error_text=DISABLE_FANUC_STYLE_SUB set in INI file, but found m98",
|
|
].join("\n"),
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"browser_nc_files_3d_chips_context",
|
|
await runVendorNcFileWithToolTable(interp, "3D_Chips.ngc"),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=4706",
|
|
"file_execute_count=4706",
|
|
"file_saw_error=0",
|
|
"run_step phase=execute step=18 rc=2 line=18",
|
|
"statement_uri=N50T%23%3Ctoolno%3EM6",
|
|
"run_step phase=execute step=19 rc=0 line=19",
|
|
"statement_uri=N60M8",
|
|
"canon_event=CHANGE_TOOL",
|
|
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
|
|
"canon_event=STRAIGHT_TRAVERSE line=21 x=0 y=0 z=10",
|
|
"canon_event=STRAIGHT_FEED line=23 x=53 y=-56.128 z=-25.372",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=",
|
|
"absent=Requested tool 1 not found",
|
|
].join("\n"),
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"browser_nc_files_arcspiral",
|
|
await runVendorNcFile(interp, "arcspiral.ngc"),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=1008",
|
|
"file_execute_count=1008",
|
|
"file_saw_error=0",
|
|
"canon_event=STRAIGHT_TRAVERSE line=3 x=0 y=0 z=1",
|
|
"canon_event=STRAIGHT_FEED line=6 x=1.72464 y=-1.01273 z=-0.1",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=",
|
|
].join("\n"),
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"browser_nc_files_hole_circle",
|
|
await runVendorNcFile(interp, "hole-circle.ngc"),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=12",
|
|
"file_execute_count=12",
|
|
"file_saw_error=0",
|
|
"canon_event=STRAIGHT_TRAVERSE line=7 x=1.5 y=0 z=0",
|
|
"canon_event=STRAIGHT_FEED line=10 x=0.75 y=1.29904 z=-0.4",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=",
|
|
].join("\n"),
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"browser_nc_files_factorial",
|
|
await runVendorNcFile(interp, "factorial.ngc"),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=11",
|
|
"file_execute_count=11",
|
|
"file_saw_error=0",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=",
|
|
].join("\n"),
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"browser_nc_files_m6demo",
|
|
await runVendorNcFile(interp, "m6demo.ngc"),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=88",
|
|
"file_execute_count=88",
|
|
"file_saw_error=0",
|
|
"canon_event=SET_G5X_OFFSET index=1",
|
|
"canon_event=STOP_SPINDLE_TURNING spindle=0",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=",
|
|
].join("\n"),
|
|
);
|
|
|
|
const foam = await loadSimMachineFiles("axis/foam", "axis_foam.ini", "foam.ngc");
|
|
verifyExpectedOutput(
|
|
"browser_sim_axis_foam_uv",
|
|
interp.runSimConfigProgram({
|
|
files: foam.files,
|
|
programPath: foam.programPath,
|
|
iniPath: foam.iniPath,
|
|
}),
|
|
[
|
|
"file_open=0",
|
|
"file_saw_error=0",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=Bad character 'u' used",
|
|
"absent=Bad character 'v' used",
|
|
].join("\n"),
|
|
);
|
|
|
|
const bridgeMillSim = await loadSimMachineFiles(
|
|
"axis/vismach/5axis/bridgemill",
|
|
"5axis.ini",
|
|
"5axisgui.ngc",
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_sim_axis_bridgemill_w",
|
|
interp.runSimConfigProgram({
|
|
files: bridgeMillSim.files,
|
|
programPath: bridgeMillSim.programPath,
|
|
iniPath: bridgeMillSim.iniPath,
|
|
executionMode: "fiveAxisRemap",
|
|
}),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_parsed_remap_count=3",
|
|
"fiveaxis_remaps_ready=1",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=2197",
|
|
"fiveaxis_file_execute_count=2197",
|
|
"fiveaxis_file_finish_count=137",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
"absent=Bad character 'w' used",
|
|
].join("\n"),
|
|
);
|
|
|
|
const geometry = await loadSimMachineFiles(
|
|
"axis/geometry",
|
|
"xyzc.ini",
|
|
"xyzc.ngc",
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_sim_axis_geometry_xyzc_user_m110",
|
|
interp.runSimConfigProgram({
|
|
files: geometry.files,
|
|
programPath: geometry.programPath,
|
|
iniPath: geometry.iniPath,
|
|
}),
|
|
[
|
|
"file_open=0",
|
|
"file_saw_error=0",
|
|
"canon_event=USER_M_COMMAND code=M110",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=Unknown m code used: M110",
|
|
"absent=Bad character 'c' used",
|
|
].join("\n"),
|
|
);
|
|
|
|
const gladevcpProbe = await loadSimMachineFiles(
|
|
"axis/gladevcp",
|
|
"gladevcp_panel.ini",
|
|
"probe.ngc",
|
|
);
|
|
await verifySimBoundaryDeclaration("browser_sim_axis_gladevcp_probe_boundary", {
|
|
machineRel: "axis/gladevcp",
|
|
iniFile: "gladevcp_panel.ini",
|
|
executionFiles: ["probe.ngc"],
|
|
expectedDependencies: ["hal_process", "python_runtime", "ui_process"],
|
|
expectedProcesses: {
|
|
halRuntime: true,
|
|
uiRuntime: true,
|
|
haluiRuntime: false,
|
|
pythonRuntime: true,
|
|
},
|
|
expectedPythonUiReferences: ["-u hitcounter.py manual-example.ui"],
|
|
expectedPythonRemapReferences: [],
|
|
expectedCoverage: "file",
|
|
});
|
|
verifyExpectedOutput(
|
|
"browser_sim_axis_gladevcp_probe",
|
|
interp.runSimConfigProgram({
|
|
files: gladevcpProbe.files,
|
|
programPath: gladevcpProbe.programPath,
|
|
iniPath: gladevcpProbe.iniPath,
|
|
}),
|
|
[
|
|
"file_open=0",
|
|
"file_saw_error=0",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=",
|
|
].join("\n"),
|
|
);
|
|
|
|
const externalOffsetsMacro = wrapSimProgramFiles(
|
|
await loadSimMachineFiles(
|
|
"axis/external_offsets",
|
|
"dynamic_offsets.ini",
|
|
"circles.ngc",
|
|
),
|
|
"__wrapped_circles.ngc",
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_sim_axis_external_offsets_circles_macro",
|
|
interp.runSimConfigProgram({
|
|
files: externalOffsetsMacro.files,
|
|
programPath: externalOffsetsMacro.programPath,
|
|
iniPath: externalOffsetsMacro.iniPath,
|
|
}),
|
|
[
|
|
"file_open=0",
|
|
"file_execute_45=1",
|
|
"statement_uri=M2",
|
|
"file_read_count=45",
|
|
"file_execute_count=45",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: info: Multiple circles at radius R, use 0 for current val",
|
|
"canon_event=PROGRAM_END",
|
|
"post_execute.origin_index=1",
|
|
"post_execute.mist=0",
|
|
"post_execute.flood=0",
|
|
"absent=file_error_text=",
|
|
].join("\n"),
|
|
);
|
|
|
|
for (const externalOffsetCase of [
|
|
{
|
|
name: "dyn_demo",
|
|
program: "dyn_demo.ngc",
|
|
ini: "dynamic_offsets.ini",
|
|
readCount: 833,
|
|
firstTraverse: "canon_event=STRAIGHT_TRAVERSE line=21 x=0 y=0 z=0",
|
|
firstFeed: "canon_event=STRAIGHT_FEED line=26 x=9 y=0 z=1.5",
|
|
},
|
|
{
|
|
name: "eoffsets",
|
|
program: "eoffsets.ngc",
|
|
ini: "eoffsets.ini",
|
|
readCount: 35,
|
|
firstTraverse: "canon_event=STRAIGHT_TRAVERSE line=24 x=0 y=0 z=0",
|
|
firstFeed: "canon_event=STRAIGHT_FEED line=27 x=2.25 y=2.64 z=0",
|
|
},
|
|
{
|
|
name: "jwp_z",
|
|
program: "jwp_z.ngc",
|
|
ini: "jwp_z.ini",
|
|
readCount: 35,
|
|
firstTraverse: "canon_event=STRAIGHT_TRAVERSE line=17 x=0 y=0 z=0.5",
|
|
firstFeed: "canon_event=STRAIGHT_FEED line=24 x=2.5 y=3.3 z=0",
|
|
},
|
|
{
|
|
name: "opa_demo",
|
|
program: "opa_demo.ngc",
|
|
ini: "opa.ini",
|
|
readCount: 58,
|
|
firstTraverse: "canon_event=STRAIGHT_TRAVERSE line=13 x=1 y=0 z=0",
|
|
firstFeed: "canon_event=STRAIGHT_FEED line=35 x=1 y=0 z=0",
|
|
},
|
|
]) {
|
|
const externalOffsets = await loadSimMachineFiles(
|
|
"axis/external_offsets",
|
|
externalOffsetCase.ini,
|
|
externalOffsetCase.program,
|
|
);
|
|
verifyExpectedOutput(
|
|
`browser_sim_axis_external_offsets_${externalOffsetCase.name}_user_m111`,
|
|
interp.runSimConfigProgram({
|
|
files: externalOffsets.files,
|
|
programPath: externalOffsets.programPath,
|
|
iniPath: externalOffsets.iniPath,
|
|
}),
|
|
[
|
|
"file_open=0",
|
|
`file_read_count=${externalOffsetCase.readCount}`,
|
|
`file_execute_count=${externalOffsetCase.readCount}`,
|
|
"file_saw_error=0",
|
|
"canon_event=USER_M_COMMAND code=M111",
|
|
externalOffsetCase.firstTraverse,
|
|
externalOffsetCase.firstFeed,
|
|
"canon_event=PROGRAM_END",
|
|
"absent=Unknown m code used: M111",
|
|
"absent=Bad character",
|
|
"absent=Cannot use axis values",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
const melfaSim = await loadSimMachineFiles(
|
|
"axis/vismach/melfa-sim",
|
|
"melfa.ini",
|
|
"example.ngc",
|
|
);
|
|
await verifySimBoundaryDeclaration("browser_sim_axis_vismach_melfa_boundary", {
|
|
machineRel: "axis/vismach/melfa-sim",
|
|
iniFile: "melfa.ini",
|
|
executionFiles: ["example.ngc"],
|
|
expectedDependencies: ["hal_process", "halui_mdi_process", "ui_process"],
|
|
expectedProcesses: {
|
|
halRuntime: true,
|
|
uiRuntime: true,
|
|
haluiRuntime: true,
|
|
pythonRuntime: false,
|
|
},
|
|
expectedCoverage: "remap",
|
|
});
|
|
verifyExpectedOutput(
|
|
"browser_sim_axis_vismach_melfa",
|
|
interp.runSimConfigProgram({
|
|
files: melfaSim.files,
|
|
programPath: melfaSim.programPath,
|
|
iniPath: melfaSim.iniPath,
|
|
executionMode: "fiveAxisRemap",
|
|
}),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const pumaCube = await loadSimMachineFiles(
|
|
"axis/vismach/puma",
|
|
"puma_cube.ini",
|
|
"puma_cube.ngc",
|
|
);
|
|
await verifySimBoundaryDeclaration("browser_sim_axis_vismach_puma_cube_boundary", {
|
|
machineRel: "axis/vismach/puma",
|
|
iniFile: "puma_cube.ini",
|
|
executionFiles: ["puma_cube.ngc"],
|
|
expectedDependencies: ["hal_process", "halui_mdi_process", "ui_process"],
|
|
expectedProcesses: {
|
|
halRuntime: true,
|
|
uiRuntime: true,
|
|
haluiRuntime: true,
|
|
pythonRuntime: false,
|
|
},
|
|
expectedCoverage: "file",
|
|
});
|
|
verifyExpectedOutput(
|
|
"browser_sim_axis_vismach_puma_cube",
|
|
interp.runSimConfigProgram({
|
|
files: pumaCube.files,
|
|
programPath: pumaCube.programPath,
|
|
iniPath: pumaCube.iniPath,
|
|
}),
|
|
[
|
|
"file_open=0",
|
|
"file_saw_error=0",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=",
|
|
].join("\n"),
|
|
);
|
|
|
|
const woodpeckerOnAbort = await loadSimMachineFiles(
|
|
"woodpecker",
|
|
"woodpecker.ini",
|
|
"on_abort.ngc",
|
|
);
|
|
await verifySimBoundaryDeclaration("browser_sim_woodpecker_on_abort_boundary", {
|
|
machineRel: "woodpecker",
|
|
iniFile: "woodpecker.ini",
|
|
executionFiles: ["on_abort.ngc"],
|
|
expectedDependencies: ["hal_process", "ui_process"],
|
|
expectedProcesses: {
|
|
halRuntime: true,
|
|
uiRuntime: true,
|
|
haluiRuntime: false,
|
|
pythonRuntime: false,
|
|
},
|
|
expectedCoverage: "file",
|
|
});
|
|
verifyExpectedOutput(
|
|
"browser_sim_woodpecker_on_abort",
|
|
interp.runSimConfigProgram({
|
|
files: woodpeckerOnAbort.files,
|
|
programPath: woodpeckerOnAbort.programPath,
|
|
iniPath: woodpeckerOnAbort.iniPath,
|
|
}),
|
|
[
|
|
"file_open=0",
|
|
"run_step phase=execute step=17 rc=1 line=17",
|
|
"statement_uri=m2",
|
|
"file_read_count=17",
|
|
"file_execute_count=17",
|
|
"file_saw_error=0",
|
|
"canon_event=ON_RESET",
|
|
"canon_event=PROGRAM_END",
|
|
"post_execute.origin_index=1",
|
|
"post_execute.mist=0",
|
|
"post_execute.flood=0",
|
|
"absent=file_error_text=",
|
|
].join("\n"),
|
|
);
|
|
|
|
await verifySimHardBlockedBoundary("browser_sim_axis_vismach_millturn_boundary", {
|
|
machineRel: "axis/vismach/millturn",
|
|
iniFile: "millturn.ini",
|
|
executionFiles: [
|
|
"example.ngc",
|
|
"remap_subs/428remap.ngc",
|
|
"remap_subs/429remap.ngc",
|
|
],
|
|
expectedBlockedKind: "L4-USER-M-PROCESS",
|
|
expectedDependencies: [
|
|
"external_user_m_process",
|
|
"hal_process",
|
|
"halui_mdi_process",
|
|
"ui_process",
|
|
],
|
|
expectedProcesses: {
|
|
halRuntime: true,
|
|
uiRuntime: true,
|
|
haluiRuntime: true,
|
|
pythonRuntime: false,
|
|
},
|
|
expectedUserMCodes: ["M128", "M129"],
|
|
expectedUnstagedUserMCodes: ["M128", "M129"],
|
|
expectedPythonUiReferences: [],
|
|
expectedPythonRemapReferences: [],
|
|
expectedVendoredUserMCount: 0,
|
|
expectedRequiresExternalUserM: true,
|
|
expectedToolDatabaseProgram: "",
|
|
});
|
|
|
|
await verifySimHardBlockedBoundary("browser_sim_axis_db_demo_tool_db_boundary", {
|
|
machineRel: "axis/db_demo",
|
|
iniFile: "db_nonran.ini",
|
|
executionFiles: ["base.ngc"],
|
|
expectedBlockedKind: "L4-TOOL-DB",
|
|
expectedDependencies: ["python_runtime", "tool_database_process"],
|
|
expectedProcesses: {
|
|
halRuntime: false,
|
|
uiRuntime: false,
|
|
haluiRuntime: false,
|
|
pythonRuntime: true,
|
|
},
|
|
expectedUserMCodes: [],
|
|
expectedUnstagedUserMCodes: [],
|
|
expectedPythonUiReferences: ["./db_nonran.py"],
|
|
expectedPythonRemapReferences: [],
|
|
expectedVendoredUserMCount: 0,
|
|
expectedRequiresExternalUserM: false,
|
|
expectedToolDatabaseProgram: "./db_nonran.py",
|
|
});
|
|
|
|
const fiveAxisTrtDir = await writeFiveAxisTrtMachineFiles(interp);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_xyzac_switchkins",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTrtDir}/demos/xyzac_switchkins.ngc`,
|
|
`${fiveAxisTrtDir}/xyzac-trt.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=620",
|
|
"fiveaxis_file_execute_count=620",
|
|
"fiveaxis_file_finish_count=21",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_xyzbc_switchkins",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTrtDir}/demos/xyzbc_switchkins.ngc`,
|
|
`${fiveAxisTrtDir}/xyzbc-trt.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=620",
|
|
"fiveaxis_file_execute_count=620",
|
|
"fiveaxis_file_finish_count=21",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_xyzac_switchkins_test_1",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTrtDir}/demos/xyzac_switchkins_test_1.ngc`,
|
|
`${fiveAxisTrtDir}/xyzac-trt.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_parsed_remap_count=3",
|
|
"fiveaxis_remaps_ready=1",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=248",
|
|
"fiveaxis_file_execute_count=248",
|
|
"fiveaxis_file_finish_count=9",
|
|
"fiveaxis_file_final_rc=1",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_xyzac_switchkins_test_2",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTrtDir}/demos/xyzac_switchkins_test_2.ngc`,
|
|
`${fiveAxisTrtDir}/xyzac-trt.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_parsed_remap_count=3",
|
|
"fiveaxis_remaps_ready=1",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=120",
|
|
"fiveaxis_file_execute_count=120",
|
|
"fiveaxis_file_finish_count=3",
|
|
"fiveaxis_file_final_rc=1",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_xyzac_switchkins_test_3",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTrtDir}/demos/xyzac_switchkins_test_3.ngc`,
|
|
`${fiveAxisTrtDir}/xyzac-trt.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_parsed_remap_count=3",
|
|
"fiveaxis_remaps_ready=1",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=86",
|
|
"fiveaxis_file_execute_count=86",
|
|
"fiveaxis_file_finish_count=3",
|
|
"fiveaxis_file_final_rc=1",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_boat_xyzac",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTrtDir}/demos/boat-xyzac.ngc`,
|
|
`${fiveAxisTrtDir}/xyzac-trt.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=1927",
|
|
"fiveaxis_file_execute_count=1927",
|
|
"fiveaxis_file_finish_count=3",
|
|
"fiveaxis_file_final_rc=1",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_boat_xyzbc",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTrtDir}/demos/boat-xyzbc.ngc`,
|
|
`${fiveAxisTrtDir}/xyzbc-trt.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=1913",
|
|
"fiveaxis_file_execute_count=1913",
|
|
"fiveaxis_file_finish_count=3",
|
|
"fiveaxis_file_final_rc=1",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_impeller_7bl_xyzac",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTrtDir}/demos/impeller-7bl-xyzac.ngc`,
|
|
`${fiveAxisTrtDir}/xyzac-trt.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=4558",
|
|
"fiveaxis_file_execute_count=4558",
|
|
"fiveaxis_file_finish_count=2",
|
|
"fiveaxis_file_final_rc=1",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
const fiveAxisTdrDir = await writeFiveAxisTdrMachineFiles(interp);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_xyzab_tdr_demo",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisTdrDir}/demos/xyzab-tdr-demo.ngc`,
|
|
`${fiveAxisTdrDir}/xyzab-tdr.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parsed_remap_count=2",
|
|
"fiveaxis_remaps_ready=1",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=97",
|
|
"fiveaxis_file_execute_count=97",
|
|
"fiveaxis_file_finish_count=3",
|
|
"fiveaxis_file_final_rc=1",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=1",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
const fiveAxisBridgeMillDir = await writeFiveAxisBridgeMillMachineFiles(interp);
|
|
verifyExpectedOutput(
|
|
"browser_fiveaxis_bridgemill_5axisgui",
|
|
interp.runFiveAxisRemapFile(
|
|
`${fiveAxisBridgeMillDir}/5axisgui.ngc`,
|
|
`${fiveAxisBridgeMillDir}/5axis.ini`,
|
|
),
|
|
[
|
|
"fiveaxis_ini_open=1",
|
|
"fiveaxis_tool_table_load=0",
|
|
"fiveaxis_parse_remap_1=0",
|
|
"fiveaxis_parse_remap_2=0",
|
|
"fiveaxis_parse_remap_3=0",
|
|
"fiveaxis_parsed_remap_count=3",
|
|
"fiveaxis_remaps_ready=1",
|
|
"fiveaxis_file_open=0",
|
|
"fiveaxis_file_read_count=2197",
|
|
"fiveaxis_file_execute_count=2197",
|
|
"fiveaxis_file_finish_count=137",
|
|
"fiveaxis_file_final_rc=1",
|
|
"fiveaxis_file_reached_exit=1",
|
|
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
|
"fiveaxis_linuxcnc_remap_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const duplicateOwordFiles = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rm207.ngc",
|
|
"rm208.ngc",
|
|
];
|
|
const duplicateOwordDir = await writeRemapRegressionFiles(interp, "duplicate-o-word", duplicateOwordFiles);
|
|
assertRemapRegressionFilesStaging("duplicate-o-word", duplicateOwordFiles, duplicateOwordDir);
|
|
verifyExpectedOutput(
|
|
"browser_duplicate_oword_remap",
|
|
interp.runRemapFile(
|
|
`${duplicateOwordDir}/test.ngc`,
|
|
`${duplicateOwordDir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_parse_remap_2=0",
|
|
"remap_parsed_remap_count=2",
|
|
"remap_remaps_ready=1",
|
|
"remap_file_open=0",
|
|
"remap_file_read_count=31",
|
|
"remap_file_execute_count=31",
|
|
"remap_file_finish_count=0",
|
|
"remap_file_final_rc=3",
|
|
"remap_file_reached_endfile=1",
|
|
"remap_canon_event=MESSAGE: executing m207: run remapped m208 before o<problem>",
|
|
"remap_canon_event=MESSAGE: rm207 running remapped m208",
|
|
"remap_canon_event=MESSAGE: rm208 starting and done",
|
|
"remap_canon_event=FINISH",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const failArgs0Files = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rm400.ngc",
|
|
];
|
|
const failArgs0Dir = await writeRemapRegressionFiles(interp, "fail/args.0", failArgs0Files);
|
|
assertRemapRegressionFilesStaging("fail/args.0", failArgs0Files, failArgs0Dir);
|
|
verifyExpectedOutput(
|
|
"browser_fail_args_0_remap",
|
|
interp.runRemapFile(
|
|
`${failArgs0Dir}/test.ngc`,
|
|
`${failArgs0Dir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_parsed_remap_count=1",
|
|
"remap_remaps_ready=1",
|
|
"remap_file_open=0",
|
|
"remap_file_saw_error=0",
|
|
"remap_canon_event=MESSAGE: M400 call_level= 1.000000 remap_level=1.000000",
|
|
"remap_canon_event=MESSAGE: P word set: 47.110000",
|
|
"remap_canon_event=MESSAGE: Q word set: 8.150000",
|
|
"remap_canon_event=PROGRAM_END",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const failArgs1Files = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rm400.ngc",
|
|
];
|
|
const failArgs1Dir = await writeRemapRegressionFiles(interp, "fail/args.1", failArgs1Files);
|
|
assertRemapRegressionFilesStaging("fail/args.1", failArgs1Files, failArgs1Dir);
|
|
verifyExpectedOutput(
|
|
"browser_fail_args_1_remap",
|
|
interp.runRemapFileContinueOnError(
|
|
`${failArgs1Dir}/test.ngc`,
|
|
`${failArgs1Dir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_file_execute_1=5",
|
|
"remap_file_error_text=user-defined M400: missing: Q",
|
|
"remap_file_saw_error=1",
|
|
"remap_canon_event=PROGRAM_END",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const failArgs2Files = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rm400.ngc",
|
|
];
|
|
const failArgs2Dir = await writeRemapRegressionFiles(interp, "fail/args.2", failArgs2Files);
|
|
assertRemapRegressionFilesStaging("fail/args.2", failArgs2Files, failArgs2Dir);
|
|
verifyExpectedOutput(
|
|
"browser_fail_args_2_remap",
|
|
interp.runRemapFileContinueOnError(
|
|
`${failArgs2Dir}/test.ngc`,
|
|
`${failArgs2Dir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_file_execute_1=5",
|
|
"remap_file_error_text=user-defined M400: missing: P,Q",
|
|
"remap_file_saw_error=1",
|
|
"remap_canon_event=PROGRAM_END",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const failBodyNgcFiles = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rm400.ngc",
|
|
];
|
|
const failBodyNgcDir = await writeRemapRegressionFiles(interp, "fail/body-ngc", failBodyNgcFiles);
|
|
assertRemapRegressionFilesStaging("fail/body-ngc", failBodyNgcFiles, failBodyNgcDir);
|
|
verifyExpectedOutput(
|
|
"browser_fail_body_ngc_remap",
|
|
interp.runRemapFileContinueOnError(
|
|
`${failBodyNgcDir}/test.ngc`,
|
|
`${failBodyNgcDir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_file_read_5=5",
|
|
"remap_file_error_text=Attempt to divide by zero",
|
|
"remap_file_saw_error=1",
|
|
"remap_canon_event=MESSAGE: before M400: call_level= 0.000000 remap_level=0.000000",
|
|
"remap_canon_event=MESSAGE: in rm400: call_level= 1.000000 remap_level=1.000000",
|
|
"remap_canon_event=MESSAGE: after failed M400: call_level= 0.000000 remap_level=0.000000",
|
|
"remap_canon_event=PROGRAM_END",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m30InteractionFiles = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rm400.ngc",
|
|
];
|
|
const m30InteractionDir = await writeRemapRegressionFiles(interp, "m30-interaction", m30InteractionFiles);
|
|
assertRemapRegressionFilesStaging("m30-interaction", m30InteractionFiles, m30InteractionDir);
|
|
verifyExpectedOutput(
|
|
"browser_m30_interaction_remap",
|
|
interp.runRemapFile(
|
|
`${m30InteractionDir}/test.ngc`,
|
|
`${m30InteractionDir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_parsed_remap_count=1",
|
|
"remap_remaps_ready=1",
|
|
"remap_file_open=0",
|
|
"remap_file_read_count=4",
|
|
"remap_file_execute_count=4",
|
|
"remap_file_finish_count=0",
|
|
"remap_file_final_rc=1",
|
|
"remap_file_reached_exit=1",
|
|
"remap_canon_event=MESSAGE: m400 call_level=1.000000 remap_level=1.000000 line=2.000000",
|
|
"remap_canon_event=PROGRAM_END",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const nestedRemapsOwordFiles = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rm400.ngc",
|
|
"rm401.ngc",
|
|
"rm402.ngc",
|
|
"rm403.ngc",
|
|
];
|
|
const nestedRemapsOwordUnstagedFiles = [
|
|
"testsub.ngc",
|
|
];
|
|
const nestedRemapsOwordDir = await writeRemapRegressionFiles(
|
|
interp,
|
|
"nested-remaps-oword",
|
|
nestedRemapsOwordFiles,
|
|
);
|
|
assertRemapRegressionUnstagedOverlap(
|
|
"nested-remaps-oword",
|
|
nestedRemapsOwordFiles,
|
|
nestedRemapsOwordUnstagedFiles,
|
|
);
|
|
assertRemapRegressionFilesStaging(
|
|
"nested-remaps-oword",
|
|
nestedRemapsOwordFiles,
|
|
nestedRemapsOwordDir,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_nested_remaps_oword_remap",
|
|
interp.runRemapFile(
|
|
`${nestedRemapsOwordDir}/test.ngc`,
|
|
`${nestedRemapsOwordDir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_parse_remap_2=0",
|
|
"remap_parse_remap_3=0",
|
|
"remap_parse_remap_4=0",
|
|
"remap_parsed_remap_count=4",
|
|
"remap_remaps_ready=1",
|
|
"remap_file_open=0",
|
|
"remap_file_read_count=21",
|
|
"remap_file_execute_count=21",
|
|
"remap_file_finish_count=0",
|
|
"remap_file_final_rc=3",
|
|
"remap_file_reached_endfile=1",
|
|
"remap_canon_event=MESSAGE: main call_level=0.000000 remap_level=0.000000 line=2.000000",
|
|
"remap_canon_event=MESSAGE: m400 handler pre call_level=1.000000 remap_level=1.000000 line=2.000000",
|
|
"remap_canon_event=MESSAGE: m401 handler pre call_level=2.000000 remap_level=2.000000 line=2.000000",
|
|
"remap_canon_event=MESSAGE: m402 handler pre call_level=3.000000 remap_level=3.000000 line=2.000000",
|
|
"remap_canon_event=MESSAGE: m403 handler pre call_level=4.000000 remap_level=4.000000 line=2.000000",
|
|
"remap_canon_event=MESSAGE: m400 handler post call_level=1.000000 remap_level=1.000000 line=4.000000",
|
|
"remap_canon_event=FINISH",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const posargs0Files = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rg881.ngc",
|
|
];
|
|
const posargs0Dir = await writeRemapRegressionFiles(interp, "posargs.0", posargs0Files);
|
|
assertRemapRegressionFilesStaging("posargs.0", posargs0Files, posargs0Dir);
|
|
verifyExpectedOutput(
|
|
"browser_posargs_0_remap",
|
|
interp.runRemapFile(
|
|
`${posargs0Dir}/test.ngc`,
|
|
`${posargs0Dir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_parsed_remap_count=1",
|
|
"remap_remaps_ready=1",
|
|
"remap_file_open=0",
|
|
"remap_file_read_count=19",
|
|
"remap_file_execute_count=19",
|
|
"remap_file_finish_count=0",
|
|
"remap_file_final_rc=1",
|
|
"remap_file_reached_exit=1",
|
|
"remap_canon_event=MESSAGE: in rg881: n_args=3.000000 [1.000000] [2.000000] [3.000000] [0.000000] [0.000000]",
|
|
"remap_canon_event=MESSAGE: in rg881: n_args=4.000000 [1.000000] [2.000000] [3.000000] [4.000000] [0.000000]",
|
|
"remap_canon_event=MESSAGE: in rg881: n_args=4.000000 [1.000000] [2.000000] [3.000000] [5.000000] [0.000000]",
|
|
"remap_canon_event=MESSAGE: in rg881: n_args=5.000000 [1.000000] [2.000000] [3.000000] [4.000000] [5.000000]",
|
|
"remap_canon_event=PROGRAM_END",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const sequencingFiles = [
|
|
"test.ini",
|
|
"test.ngc",
|
|
"rg881.ngc",
|
|
"rm405.ngc",
|
|
"rm406.ngc",
|
|
"rm407.ngc",
|
|
"rm408.ngc",
|
|
"rm409.ngc",
|
|
"rm410.ngc",
|
|
];
|
|
const sequencingDir = await writeRemapRegressionFiles(interp, "sequencing", sequencingFiles);
|
|
assertRemapRegressionFilesStaging("sequencing", sequencingFiles, sequencingDir);
|
|
verifyExpectedOutput(
|
|
"browser_sequencing_remap",
|
|
interp.runRemapFile(
|
|
`${sequencingDir}/test.ngc`,
|
|
`${sequencingDir}/test.ini`,
|
|
),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_parse_remap_2=0",
|
|
"remap_parse_remap_3=0",
|
|
"remap_parse_remap_4=0",
|
|
"remap_parse_remap_5=0",
|
|
"remap_parse_remap_6=0",
|
|
"remap_parse_remap_7=0",
|
|
"remap_parsed_remap_count=7",
|
|
"remap_remaps_ready=1",
|
|
"remap_file_open=0",
|
|
"remap_file_read_count=10084",
|
|
"remap_file_execute_count=10084",
|
|
"remap_file_final_rc=1",
|
|
"remap_file_reached_exit=1",
|
|
"remap_canon_event=MESSAGE: seq=1.000000",
|
|
"remap_canon_event=MESSAGE: seq=5.000000",
|
|
"remap_canon_event=MESSAGE: seq=6.000000",
|
|
"remap_canon_event=MESSAGE: seq=7.000000",
|
|
"remap_canon_event=MESSAGE: seq=8.000000 - reset to 1",
|
|
"remap_canon_event=MESSAGE: seq=8.000000",
|
|
"remap_canon_event=MESSAGE: seq=9.000000",
|
|
"remap_canon_event=MESSAGE: seq=10.000000 - reset to 1",
|
|
"remap_canon_event=PROGRAM_END",
|
|
"remap_linuxcnc_file_execute=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const remapIoFiles = [
|
|
"test-ngc.ini",
|
|
"io_input_m66.ngc",
|
|
"io_output_m62.ngc",
|
|
"io_output_m63.ngc",
|
|
"io_output_m64.ngc",
|
|
"io_output_m65.ngc",
|
|
"io_output_m67.ngc",
|
|
"io_output_m68.ngc",
|
|
];
|
|
const remapIoDir = await writeRemapRegressionFiles(interp, "remap-io", remapIoFiles);
|
|
assertRemapRegressionFilesStaging("remap-io", remapIoFiles, remapIoDir);
|
|
const remapIoGeneratedToolTablePath = `${remapIoDir}/simpockets.tbl`;
|
|
assertRemapRegressionGeneratedFileStaging("remap-io", "simpockets.tbl", remapIoGeneratedToolTablePath);
|
|
interp.writeTextFile(remapIoGeneratedToolTablePath, "T2 P2 Z0 ;remap-io tool\n");
|
|
verifyExpectedOutput(
|
|
"browser_remap_io_ngc_mdi",
|
|
interp.runRemapIoMdiSequence(`${remapIoDir}/test-ngc.ini`),
|
|
[
|
|
"remap_ini_open=1",
|
|
"remap_subroutine_path_count=1",
|
|
"remap_parse_remap_1=0",
|
|
"remap_parse_remap_7=0",
|
|
"remap_parsed_remap_count=7",
|
|
"remap_remaps_ready=1",
|
|
"remap_mdi_M62 P1_execute_0=0",
|
|
"remap_mdi_M66 P1_execute_0=2",
|
|
"remap_mdi_M66 P1_execute_1=0",
|
|
"remap_mdi_M66 E1 L0_execute_0=2",
|
|
"remap_mdi_M66 E1 L0_execute_1=0",
|
|
"remap_mdi_parameter_5399=42.13",
|
|
"remap_mdi_parameter_5399=-13.42",
|
|
"remap_mdi_canon_event=SET_MOTION_OUTPUT_BIT index=0",
|
|
"remap_mdi_canon_event=CLEAR_MOTION_OUTPUT_BIT index=0",
|
|
"remap_mdi_canon_event=SET_AUX_OUTPUT_BIT index=0",
|
|
"remap_mdi_canon_event=CLEAR_AUX_OUTPUT_BIT index=0",
|
|
"remap_mdi_canon_event=WAIT index=0 input_type=1 wait_type=0 timeout=0",
|
|
"remap_mdi_canon_event=WAIT index=0 input_type=0 wait_type=0 timeout=0",
|
|
"remap_mdi_canon_event=SET_MOTION_OUTPUT_VALUE index=0 value=42.13",
|
|
"remap_mdi_canon_event=SET_AUX_OUTPUT_VALUE index=0 value=-13.42",
|
|
"remap_io_ngc_linuxcnc_mdi_sequence=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const doWhileBreakNgcFiles = [
|
|
"bug.ngc",
|
|
"test.ngc",
|
|
];
|
|
const doWhileBreakDir = await writeInterpRegressionFiles(
|
|
interp,
|
|
"do-while-break",
|
|
doWhileBreakNgcFiles,
|
|
);
|
|
assertInterpRegressionFilesStaging("do-while-break", doWhileBreakNgcFiles, doWhileBreakDir);
|
|
const doWhileBreakPath = `${doWhileBreakDir}/test.ngc`;
|
|
verifyExpectedOutput(
|
|
"browser_interp_do_while_break",
|
|
interp.runFile(doWhileBreakPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=30",
|
|
"file_execute_count=30",
|
|
"canon_event=MESSAGE: must-execute-outer",
|
|
"canon_event=MESSAGE: must-execute-nested",
|
|
"canon_event=MESSAGE: must-execute-inner",
|
|
"canon_event=MESSAGE: post-inner-while",
|
|
"canon_event=MESSAGE: post-nested-while",
|
|
"canon_event=MESSAGE: post-outer-while",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=canon_event=MESSAGE: do-not-execute",
|
|
].join("\n"),
|
|
);
|
|
|
|
const doWhileBreakBugPath = `${doWhileBreakDir}/bug.ngc`;
|
|
verifyExpectedOutput(
|
|
"browser_interp_do_while_break_bug",
|
|
interp.runFile(doWhileBreakBugPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=7",
|
|
"file_execute_count=7",
|
|
"file_saw_error=0",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=@end of do-while-loop",
|
|
"absent=file_saw_error=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const owordBug315Path = await writeInterpRegressionFile(
|
|
interp,
|
|
"oword-bug315",
|
|
"test.ngc",
|
|
);
|
|
const owordBug315NgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (owordBug315NgcFiles.length !== 1 || new Set(owordBug315NgcFiles).size !== owordBug315NgcFiles.length) {
|
|
throw new Error("browser_interp_oword_bug315_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("oword-bug315", "test.ngc", owordBug315Path);
|
|
verifyExpectedOutput(
|
|
"browser_interp_oword_bug315",
|
|
interp.runFile(owordBug315Path),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=23",
|
|
"file_execute_count=23",
|
|
"canon_event=MESSAGE: running sub",
|
|
"canon_event=MESSAGE: breaking out of sub",
|
|
"canon_event=MESSAGE: done",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const owordBug315P2Path = await writeInterpRegressionFile(
|
|
interp,
|
|
"oword-bug315-p2",
|
|
"test.ngc",
|
|
);
|
|
const owordBug315P2NgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (owordBug315P2NgcFiles.length !== 1 || new Set(owordBug315P2NgcFiles).size !== owordBug315P2NgcFiles.length) {
|
|
throw new Error("browser_interp_oword_bug315_p2_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("oword-bug315-p2", "test.ngc", owordBug315P2Path);
|
|
verifyExpectedOutput(
|
|
"browser_interp_oword_bug315_p2",
|
|
interp.runFile(owordBug315P2Path),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=26",
|
|
"file_execute_count=26",
|
|
"canon_event=STRAIGHT_TRAVERSE line=7 x=2 y=2 z=2",
|
|
"canon_event=MESSAGE: executing this one",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=canon_event=MESSAGE: not executing this one",
|
|
].join("\n"),
|
|
);
|
|
|
|
const existsPath = await writeInterpRegressionFile(interp, "exists", "test.ngc");
|
|
const existsNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (existsNgcFiles.length !== 1 || new Set(existsNgcFiles).size !== existsNgcFiles.length) {
|
|
throw new Error("browser_interp_exists_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("exists", "test.ngc", existsPath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_exists",
|
|
interp.runFile(existsPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=7",
|
|
"file_execute_count=7",
|
|
"canon_event=STRAIGHT_TRAVERSE line=2 x=1 y=0 z=1",
|
|
"canon_event=COMMENT: comment",
|
|
"canon_event=STRAIGHT_TRAVERSE line=6 x=1 y=0 z=0",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const returnValuePath = await writeInterpRegressionFile(
|
|
interp,
|
|
"return-value",
|
|
"test.ngc",
|
|
);
|
|
const returnValueNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (returnValueNgcFiles.length !== 1 || new Set(returnValueNgcFiles).size !== returnValueNgcFiles.length) {
|
|
throw new Error("browser_interp_return_value_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("return-value", "test.ngc", returnValuePath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_return_value",
|
|
interp.runFile(returnValuePath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=75",
|
|
"file_execute_count=75",
|
|
"canon_event=MESSAGE: line 6.000000: _value: - expected 0.000000, got 0.000000",
|
|
"canon_event=MESSAGE: line 28.000000: call with arg1=2.000000 expect 246.000000, got 246.000000",
|
|
"canon_event=MESSAGE: line 37.000000: call with arg1=-1.000000 expect 0.000000, got 0.000000",
|
|
"canon_event=MESSAGE: line 47.000000: call with arg1=0.000000 expect 4712.000000, got 4712.000000",
|
|
"canon_event=MESSAGE: line 53.000000: _value=4712.000000 - expected 4712.000000",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=fail:",
|
|
].join("\n"),
|
|
);
|
|
|
|
const subsFollowMainPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"subs-follow-main",
|
|
"test.ngc",
|
|
);
|
|
const subsFollowMainNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (subsFollowMainNgcFiles.length !== 1 || new Set(subsFollowMainNgcFiles).size !== subsFollowMainNgcFiles.length) {
|
|
throw new Error("browser_interp_subs_follow_main_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("subs-follow-main", "test.ngc", subsFollowMainPath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_subs_follow_main",
|
|
interp.runFile(subsFollowMainPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=9",
|
|
"file_execute_count=9",
|
|
"canon_event=COMMENT: Subs may follow main program ",
|
|
"canon_event=PALLET_SHUTTLE",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const fractionalLinenumbersPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"fractional-linenumbers",
|
|
"test.ngc",
|
|
);
|
|
const fractionalLinenumbersNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (fractionalLinenumbersNgcFiles.length !== 1 || new Set(fractionalLinenumbersNgcFiles).size !== fractionalLinenumbersNgcFiles.length) {
|
|
throw new Error("browser_interp_fractional_linenumbers_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("fractional-linenumbers", "test.ngc", fractionalLinenumbersPath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_fractional_linenumbers",
|
|
interp.runFile(fractionalLinenumbersPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=4",
|
|
"file_execute_count=4",
|
|
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=300",
|
|
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=600",
|
|
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=1200",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const crazyPathsPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"crazy-paths",
|
|
"test.ngc",
|
|
);
|
|
const crazyPathsNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (crazyPathsNgcFiles.length !== 1 || new Set(crazyPathsNgcFiles).size !== crazyPathsNgcFiles.length) {
|
|
throw new Error("browser_interp_crazy_paths_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("crazy-paths", "test.ngc", crazyPathsPath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_crazy_paths",
|
|
interp.runFile(crazyPathsPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=1526",
|
|
"file_execute_count=1526",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
|
|
"canon_event=ARC_FEED line=8 first_end=-99.2047 second_end=-102.892 first_axis=-98.9043 second_axis=-102.097 rotation=-1 axis_end_point=0",
|
|
"canon_event=ARC_FEED line=103 first_end=-40.3437 second_end=-103.278 first_axis=-45.0196 second_axis=-102.442 rotation=-1 axis_end_point=0",
|
|
"canon_event=STRAIGHT_FEED line=260 x=3.58919 y=30.3694 z=0",
|
|
"canon_event=ARC_FEED line=445 first_end=101.539 second_end=44.5843 first_axis=100.896 second_axis=46.212 rotation=1 axis_end_point=0",
|
|
"canon_event=STRAIGHT_FEED line=469 x=98.8154 y=102.176 z=0",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const camNisleyFiles = [
|
|
"cam.ngc",
|
|
"test.tbl",
|
|
];
|
|
const camNisleyDir = await writeInterpRegressionFiles(interp, "cam-nisley", camNisleyFiles);
|
|
assertInterpRegressionFilesStaging("cam-nisley", camNisleyFiles, camNisleyDir);
|
|
interp.writeTextFile(
|
|
`${camNisleyDir}/test.ini`,
|
|
[
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = test.tbl",
|
|
"[TRAJ]",
|
|
"COORDINATES = X Y Z A B C U V W",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_cam_nisley",
|
|
interp.runFileWithIni(`${camNisleyDir}/cam.ngc`, `${camNisleyDir}/test.ini`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=1584",
|
|
"file_execute_count=1584",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: Ed Nisley - Nov 2006 - Mar 2007",
|
|
"canon_event=SELECT_TOOL tool=1",
|
|
"canon_event=CHANGE_TOOL",
|
|
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
|
|
"canon_event=ARC_FEED line=223 first_end=1.91351e-18 second_end=-15.1438",
|
|
"canon_event=STRAIGHT_TRAVERSE line=241 x=75 y=0 z=75",
|
|
"canon_event=MESSAGE: Done!",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=Requested tool 1 not found in the tool table",
|
|
].join("\n"),
|
|
);
|
|
|
|
const camNisleyBareInterp = await createLinuxCncInterpSdk({
|
|
locateFile(path) {
|
|
if (path === "linuxcnc_interp.wasm") {
|
|
return "../../build/wasm/core/linuxcnc_interp.wasm";
|
|
}
|
|
return path;
|
|
},
|
|
print() {},
|
|
printErr(message) {
|
|
console.error(message);
|
|
},
|
|
});
|
|
await writeFetchedTextFile(
|
|
camNisleyBareInterp,
|
|
"../../vendor/linuxcnc/tests/interp/cam-nisley/cam.ngc",
|
|
"/work/browser-interp/cam-nisley-bare/cam.ngc",
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_cam_nisley_bare",
|
|
camNisleyBareInterp.runFile("/work/browser-interp/cam-nisley-bare/cam.ngc"),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=57",
|
|
"file_execute_count=57",
|
|
"file_saw_error=1",
|
|
"file_error_text=Requested tool 1 not found in the tool table",
|
|
].join("\n"),
|
|
);
|
|
|
|
const namedparamBug424Path = await writeInterpRegressionFile(
|
|
interp,
|
|
"namedparam-bug424",
|
|
"test.ngc",
|
|
);
|
|
const namedparamBug424NgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (namedparamBug424NgcFiles.length !== 1 || new Set(namedparamBug424NgcFiles).size !== namedparamBug424NgcFiles.length) {
|
|
throw new Error("browser_interp_namedparam_bug424_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("namedparam-bug424", "test.ngc", namedparamBug424Path);
|
|
verifyExpectedOutput(
|
|
"browser_interp_namedparam_bug424",
|
|
interp.runFile(namedparamBug424Path),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=6",
|
|
"file_execute_count=6",
|
|
"setup.current_x=2",
|
|
"setup.current_y=3",
|
|
"setup.current_z=4",
|
|
"canon_event=USE_LENGTH_UNITS units=1",
|
|
"canon_event=STRAIGHT_TRAVERSE line=5 x=2 y=3 z=4",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const flowsnakePath = await writeInterpRegressionFile(
|
|
interp,
|
|
"flowsnake",
|
|
"flowsnake.ngc",
|
|
);
|
|
const flowsnakeNgcFiles = [
|
|
"flowsnake.ngc",
|
|
];
|
|
if (flowsnakeNgcFiles.length !== 1 || new Set(flowsnakeNgcFiles).size !== flowsnakeNgcFiles.length) {
|
|
throw new Error("browser_interp_flowsnake_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("flowsnake", "flowsnake.ngc", flowsnakePath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_flowsnake",
|
|
interp.runFile(flowsnakePath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=6414",
|
|
"file_execute_count=6414",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: Program to mill a flowsnake",
|
|
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
|
|
"canon_event=STRAIGHT_TRAVERSE line=33 x=0.25 y=1 z=1",
|
|
"canon_event=STRAIGHT_FEED line=13 x=3.75 y=1 z=0",
|
|
"canon_event=STRAIGHT_FEED line=13 x=2 y=3.95 z=0",
|
|
"canon_event=STRAIGHT_FEED line=13 x=0.25 y=1 z=0",
|
|
"canon_event=STOP_SPINDLE_TURNING spindle=0",
|
|
"setup.current_x=0.25",
|
|
"setup.current_y=1",
|
|
"setup.current_z=1",
|
|
].join("\n"),
|
|
);
|
|
|
|
const insideCornersPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"inside-corners",
|
|
"test.ngc",
|
|
);
|
|
const insideCornersNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (insideCornersNgcFiles.length !== 1 || new Set(insideCornersNgcFiles).size !== insideCornersNgcFiles.length) {
|
|
throw new Error("browser_interp_inside_corners_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("inside-corners", "test.ngc", insideCornersPath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_inside_corners",
|
|
interp.runFile(insideCornersPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=397",
|
|
"file_execute_count=397",
|
|
"file_saw_error=0",
|
|
"canon_event=PROGRAM_STOP",
|
|
"canon_event=DWELL seconds=3",
|
|
"canon_event=SELECT_PLANE plane=1",
|
|
"canon_event=SELECT_PLANE plane=3",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
|
|
"canon_event=ARC_FEED line=16 first_end=1.2281 second_end=1.3456 first_axis=0.628 second_axis=1.0458 rotation=1 axis_end_point=-0.1",
|
|
"canon_event=STRAIGHT_FEED line=14 x=0.701154 y=0.162499 z=-0.1",
|
|
"canon_event=ARC_FEED line=74 first_end=0.298564 second_end=1.82077 first_axis=0.3459 second_axis=1.7626 rotation=1 axis_end_point=0",
|
|
"canon_event=STRAIGHT_FEED line=107 x=0 y=0 z=1",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const inverseTimeWithCompPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"inverse-time-with-comp",
|
|
"inverse.ngc",
|
|
);
|
|
const inverseTimeWithCompNgcFiles = [
|
|
"inverse.ngc",
|
|
];
|
|
if (inverseTimeWithCompNgcFiles.length !== 1 || new Set(inverseTimeWithCompNgcFiles).size !== inverseTimeWithCompNgcFiles.length) {
|
|
throw new Error("browser_interp_inverse_time_with_comp_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging(
|
|
"inverse-time-with-comp",
|
|
"inverse.ngc",
|
|
inverseTimeWithCompPath,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_inverse_time_with_comp",
|
|
interp.runFile(inverseTimeWithCompPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=68",
|
|
"file_execute_count=68",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
|
|
"canon_event=COMMENT: interpreter: feed mode set to inverse time",
|
|
"canon_event=COMMENT: interpreter: feed mode set to units per minute",
|
|
"canon_event=SET_FEED_RATE rate=30",
|
|
"canon_event=SET_FEED_RATE rate=20",
|
|
"canon_event=SET_FEED_RATE rate=40",
|
|
"canon_event=SET_FEED_RATE rate=33",
|
|
"canon_event=SET_FEED_RATE rate=10",
|
|
"canon_event=SET_FEED_RATE rate=25",
|
|
"canon_event=STRAIGHT_FEED line=5 x=1 y=0 z=0",
|
|
"canon_event=ARC_FEED line=6 first_end=1.2 second_end=0",
|
|
"canon_event=STRAIGHT_FEED line=13 x=0.2 y=3.82918 z=0",
|
|
"canon_event=ARC_FEED line=14 first_end=1.22465e-17 second_end=5.2",
|
|
"canon_event=ARC_FEED line=14 first_end=1.22465e-17 second_end=4.2",
|
|
"canon_event=ARC_FEED line=14 first_end=1.22465e-17 second_end=4.8",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation off",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const ccompLatheCompDir = await writeCcompRegressionFiles(interp, "lathe-comp");
|
|
assertCcompRegressionFilesStaging("lathe-comp", ccompLatheCompDir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_ccomp_lathe_comp",
|
|
interp.runFileWithIni(
|
|
`${ccompLatheCompDir}/test.ngc`,
|
|
`${ccompLatheCompDir}/test.ini`,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=49",
|
|
"file_execute_count=49",
|
|
"file_saw_error=0",
|
|
"canon_event=SELECT_PLANE plane=3",
|
|
"canon_event=SELECT_TOOL tool=2",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
|
|
"canon_event=SELECT_TOOL tool=7",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on right",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const ccompMillG90G91G92Dir = await writeCcompRegressionFiles(
|
|
interp,
|
|
"mill-g90g91g92",
|
|
);
|
|
assertCcompRegressionFilesStaging("mill-g90g91g92", ccompMillG90G91G92Dir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_ccomp_mill_g90g91g92",
|
|
interp.runFileWithIni(
|
|
`${ccompMillG90G91G92Dir}/test.ngc`,
|
|
`${ccompMillG90G91G92Dir}/test.ini`,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=28",
|
|
"file_execute_count=28",
|
|
"file_saw_error=0",
|
|
"canon_event=SELECT_TOOL tool=3",
|
|
"canon_event=SET_G92_OFFSET x=0 y=0 z=0.5",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation off",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const ccompMillLineArcEntryDir = await writeCcompRegressionFiles(
|
|
interp,
|
|
"mill-line-arc-entry",
|
|
);
|
|
assertCcompRegressionFilesStaging("mill-line-arc-entry", ccompMillLineArcEntryDir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_ccomp_mill_line_arc_entry",
|
|
interp.runFileWithIni(
|
|
`${ccompMillLineArcEntryDir}/test.ngc`,
|
|
`${ccompMillLineArcEntryDir}/test.ini`,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=33",
|
|
"file_execute_count=33",
|
|
"file_saw_error=0",
|
|
"canon_event=SELECT_TOOL tool=4",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
|
|
"canon_event=ARC_FEED line=23 first_end=2 second_end=3.01969",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation off",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const ccompMillZchangesDir = await writeCcompRegressionFiles(interp, "mill-zchanges");
|
|
assertCcompRegressionFilesStaging("mill-zchanges", ccompMillZchangesDir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_ccomp_mill_zchanges",
|
|
interp.runFileWithIni(
|
|
`${ccompMillZchangesDir}/test.ngc`,
|
|
`${ccompMillZchangesDir}/test.ini`,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=81",
|
|
"file_execute_count=81",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation on left",
|
|
"canon_event=ARC_FEED line=15 first_end=0.5 second_end=0.752461",
|
|
"canon_event=STRAIGHT_FEED line=54 x=0.49826 y=-0.24826 z=0",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation off",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const badInterpFixtures = [
|
|
{
|
|
name: "a_in_canned_cycle",
|
|
file: "a-in-canned-cycle.ngc",
|
|
readCount: 3,
|
|
executeCount: 2,
|
|
errorText: "Cannot put an a in canned cycle",
|
|
},
|
|
{
|
|
name: "a_in_canned_cycle2",
|
|
file: "a-in-canned-cycle2.ngc",
|
|
readCount: 2,
|
|
executeCount: 1,
|
|
errorText: "Cannot put an a in canned cycle",
|
|
},
|
|
{
|
|
name: "bad_arc_big_imperial_center_format",
|
|
file: "bad-arc.big.imperial.center-format.ngc",
|
|
readCount: 6,
|
|
executeCount: 6,
|
|
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X300.1428,Y0.0000) end=(X600.0000,Y0.0000) r1=300.1428 r2=299.8572 abs_err=0.2857 rel_err=0.0952%",
|
|
},
|
|
{
|
|
name: "bad_arc_big_metric_center_format",
|
|
file: "bad-arc.big.metric.center-format.ngc",
|
|
readCount: 6,
|
|
executeCount: 6,
|
|
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X3001.4284,Y0.0000) end=(X6000.0000,Y0.0000) r1=3001.4284 r2=2998.5716 abs_err=2.857 rel_err=0.0952%",
|
|
},
|
|
{
|
|
name: "bad_arc_medium_imperial_center_format",
|
|
file: "bad-arc.medium.imperial.center-format.ngc",
|
|
readCount: 6,
|
|
executeCount: 6,
|
|
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X99.8500,Y0.0000) end=(X200.0000,Y0.0000) r1=99.8500 r2=100.1500 abs_err=0.3 rel_err=0.2996%",
|
|
},
|
|
{
|
|
name: "bad_arc_medium_metric_center_format",
|
|
file: "bad-arc.medium.metric.center-format.ngc",
|
|
readCount: 6,
|
|
executeCount: 6,
|
|
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X998.5000,Y0.0000) end=(X2000.0000,Y0.0000) r1=998.5000 r2=1001.5000 abs_err=3 rel_err=0.2996%",
|
|
},
|
|
{
|
|
name: "bad_arc_small_imperial_center_format",
|
|
file: "bad-arc.small.imperial.center-format.ngc",
|
|
readCount: 6,
|
|
executeCount: 6,
|
|
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X2.4986,Y0.0000) end=(X5.0000,Y0.0000) r1=2.4986 r2=2.5014 abs_err=0.002857 rel_err=0.1142%",
|
|
},
|
|
{
|
|
name: "bad_arc_small_metric_center_format",
|
|
file: "bad-arc.small.metric.center-format.ngc",
|
|
readCount: 6,
|
|
executeCount: 6,
|
|
errorText: "Radius to end of arc differs from radius to start: start=(X0.0000,Y0.0000) center=(X24.9857,Y0.0000) end=(X50.0000,Y0.0000) r1=24.9857 r2=25.0143 abs_err=0.02857 rel_err=0.1142%",
|
|
},
|
|
{
|
|
name: "ccomp_arcexit",
|
|
file: "ccomp-arcexit.ngc",
|
|
readCount: 7,
|
|
executeCount: 7,
|
|
errorText: "The move just after exiting cutter compensation mode must be straight, not an arc",
|
|
},
|
|
{
|
|
name: "ccomp_gouging",
|
|
file: "ccomp-gouging.ngc",
|
|
readCount: 7,
|
|
executeCount: 7,
|
|
errorText: "Straight feed in concave corner cannot be reached by the tool without gouging",
|
|
},
|
|
{
|
|
name: "exists_1",
|
|
file: "exists-1.ngc",
|
|
readCount: 2,
|
|
executeCount: 1,
|
|
errorText: "Expected # reading parameter",
|
|
},
|
|
{
|
|
name: "exists_2",
|
|
file: "exists-2.ngc",
|
|
readCount: 2,
|
|
executeCount: 1,
|
|
errorText: "Expected ] reading bracketed parameter",
|
|
},
|
|
{
|
|
name: "exists_3",
|
|
file: "exists-3.ngc",
|
|
readCount: 3,
|
|
executeCount: 2,
|
|
errorText: "Parameter number out of range",
|
|
},
|
|
{
|
|
name: "exists_4",
|
|
file: "exists-4.ngc",
|
|
readCount: 2,
|
|
executeCount: 1,
|
|
errorText: "Unknown word starting with f",
|
|
},
|
|
{
|
|
name: "exists_5",
|
|
file: "exists-5.ngc",
|
|
readCount: 2,
|
|
executeCount: 1,
|
|
errorText: "Named parameter not terminated",
|
|
},
|
|
{
|
|
name: "exists_6",
|
|
file: "exists-6.ngc",
|
|
readCount: 2,
|
|
executeCount: 1,
|
|
errorText: "bad number format (conversion failed) parsing ''",
|
|
},
|
|
{
|
|
name: "exists_7",
|
|
file: "exists-7.ngc",
|
|
readCount: 2,
|
|
executeCount: 1,
|
|
errorText: "Expected ] reading bracketed parameter",
|
|
},
|
|
{
|
|
name: "nested",
|
|
file: "nested.ngc",
|
|
readCount: 6,
|
|
executeCount: 5,
|
|
errorText: "Nested subroutine definition",
|
|
},
|
|
{
|
|
name: "no_feed_rate",
|
|
file: "no-feed-rate.ngc",
|
|
readCount: 2,
|
|
executeCount: 2,
|
|
errorText: "Cannot do g1 with zero feed rate",
|
|
},
|
|
{
|
|
name: "no_ijr",
|
|
file: "no-ijr.ngc",
|
|
readCount: 2,
|
|
executeCount: 2,
|
|
errorText: "R i j k words all missing for arc",
|
|
},
|
|
{
|
|
name: "probe_no_axes",
|
|
file: "probe-no-axes.ngc",
|
|
readCount: 2,
|
|
executeCount: 1,
|
|
errorText: "All axes missing with motion code",
|
|
},
|
|
];
|
|
if (badInterpFixtures.length !== 21 || new Set(badInterpFixtures.map((fixture) => fixture.file)).size !== badInterpFixtures.length) {
|
|
throw new Error("browser_interp_bad_fixture_coverage: list drift");
|
|
}
|
|
for (const badFixture of badInterpFixtures) {
|
|
const badPath = await writeInterpRegressionFile(interp, "bad", badFixture.file);
|
|
assertInterpRegressionFileStaging("bad", badFixture.file, badPath);
|
|
verifyExpectedOutput(
|
|
`browser_interp_bad_${badFixture.name}`,
|
|
interp.runFile(badPath),
|
|
[
|
|
"file_open=0",
|
|
`file_read_count=${badFixture.readCount}`,
|
|
`file_execute_count=${badFixture.executeCount}`,
|
|
"file_saw_error=1",
|
|
`file_error_text=${badFixture.errorText}`,
|
|
"canon_event=ON_RESET",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
const g33_1Path = await writeInterpRegressionFile(interp, "g33.1", "g33.1.ngc");
|
|
const g33_1NgcFiles = [
|
|
"g33.1.ngc",
|
|
];
|
|
if (g33_1NgcFiles.length !== 1 || new Set(g33_1NgcFiles).size !== g33_1NgcFiles.length) {
|
|
throw new Error("browser_interp_g33_1_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("g33.1", "g33.1.ngc", g33_1Path);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g33_1",
|
|
interp.runFile(g33_1Path),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=5",
|
|
"file_execute_count=5",
|
|
"file_saw_error=0",
|
|
"run_step phase=execute step=4 rc=0 line=4",
|
|
"statement_uri=g33.1%20z-1.2%20k0.1",
|
|
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
|
|
"canon_event=START_SPEED_FEED_SYNCH spindle=0 feed_per_revolution=0.1 velocity_mode=0",
|
|
"canon_event=RIGID_TAP line=4 x=0 y=0 z=-1.2 scale=1",
|
|
"canon_event=STOP_SPEED_FEED_SYNCH",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const goodArcFixtures = [
|
|
{
|
|
name: "big_imperial_center_format",
|
|
file: "good-arc.big.imperial.center-format.ngc",
|
|
x: 600,
|
|
firstEnd: 600,
|
|
firstAxis: 300.14,
|
|
},
|
|
{
|
|
name: "big_metric_center_format",
|
|
file: "good-arc.big.metric.center-format.ngc",
|
|
x: 6000,
|
|
firstEnd: 6000,
|
|
firstAxis: 3001.4,
|
|
},
|
|
{
|
|
name: "medium_imperial_center_format",
|
|
file: "good-arc.medium.imperial.center-format.ngc",
|
|
x: 200,
|
|
firstEnd: 200,
|
|
firstAxis: 99.95,
|
|
},
|
|
{
|
|
name: "medium_metric_center_format",
|
|
file: "good-arc.medium.metric.center-format.ngc",
|
|
x: 2000,
|
|
firstEnd: 2000,
|
|
firstAxis: 999.5,
|
|
},
|
|
{
|
|
name: "small_imperial_center_format",
|
|
file: "good-arc.small.imperial.center-format.ngc",
|
|
x: 5,
|
|
firstEnd: 5,
|
|
firstAxis: 2.4986,
|
|
},
|
|
{
|
|
name: "small_metric_center_format",
|
|
file: "good-arc.small.metric.center-format.ngc",
|
|
x: 50,
|
|
firstEnd: 50,
|
|
firstAxis: 24.986,
|
|
},
|
|
];
|
|
if (goodArcFixtures.length !== 6 || new Set(goodArcFixtures.map((fixture) => fixture.file)).size !== goodArcFixtures.length) {
|
|
throw new Error("browser_interp_good_fixture_coverage: list drift");
|
|
}
|
|
for (const goodArcFixture of goodArcFixtures) {
|
|
const goodArcPath = await writeInterpRegressionFile(interp, "good", goodArcFixture.file);
|
|
assertInterpRegressionFileStaging("good", goodArcFixture.file, goodArcPath);
|
|
verifyExpectedOutput(
|
|
`browser_interp_good_arc_${goodArcFixture.name}`,
|
|
interp.runFile(goodArcPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=5",
|
|
"file_execute_count=5",
|
|
"file_saw_error=0",
|
|
`run_step phase=execute step=4 rc=0 line=4 x=${goodArcFixture.x} y=0`,
|
|
`canon_event=ARC_FEED line=4 first_end=${goodArcFixture.firstEnd} second_end=0 first_axis=${goodArcFixture.firstAxis} second_axis=0 rotation=-1 axis_end_point=0`,
|
|
"canon_event=PROGRAM_END",
|
|
"absent=Radius to end of arc differs from radius to start",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
const g6164Path = await writeInterpRegressionFile(interp, "g6164", "test.ngc");
|
|
const g6164NgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (g6164NgcFiles.length !== 1 || new Set(g6164NgcFiles).size !== g6164NgcFiles.length) {
|
|
throw new Error("browser_interp_g6164_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging("g6164", "test.ngc", g6164Path);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g6164",
|
|
interp.runFile(g6164Path),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=6",
|
|
"file_execute_count=6",
|
|
"canon_event=SET_MOTION_CONTROL_MODE mode=3 tolerance=0",
|
|
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=0",
|
|
"canon_event=SET_MOTION_CONTROL_MODE mode=3 tolerance=1",
|
|
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=1",
|
|
"canon_event=SET_NAIVECAM_TOLERANCE tolerance=2",
|
|
"canon_event=SET_MOTION_CONTROL_MODE mode=2 tolerance=0",
|
|
"canon_event=SET_MOTION_CONTROL_MODE mode=1 tolerance=0",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const g72Fixtures = [
|
|
{
|
|
name: "facing",
|
|
dir: "g72-facing",
|
|
file: "g72-iterations-present.ngc",
|
|
finalZ: "-39.999",
|
|
},
|
|
{
|
|
name: "missing_iteration",
|
|
dir: "g72-missing-iteration",
|
|
file: "g72-iterations-missing.ngc",
|
|
finalZ: "-40",
|
|
},
|
|
];
|
|
if (g72Fixtures.length !== 2 || new Set(g72Fixtures.map((fixture) => `${fixture.dir}/${fixture.file}`)).size !== g72Fixtures.length) {
|
|
throw new Error("browser_interp_g72_fixture_coverage: list drift");
|
|
}
|
|
for (const g72Fixture of g72Fixtures) {
|
|
const g72Path = await writeInterpRegressionFile(interp, g72Fixture.dir, g72Fixture.file);
|
|
assertInterpRegressionFileStaging(g72Fixture.dir, g72Fixture.file, g72Path);
|
|
verifyExpectedOutput(
|
|
`browser_interp_g72_${g72Fixture.name}`,
|
|
interp.runFile(g72Path),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=10",
|
|
"file_execute_count=10",
|
|
"file_saw_error=0",
|
|
"run_step phase=execute step=8 rc=0 line=8 x=100 y=0 z=0",
|
|
"canon_event=STRAIGHT_TRAVERSE line=8 x=50 y=0 z=0",
|
|
"canon_event=STRAIGHT_FEED line=-1 x=100 y=0 z=-39",
|
|
`canon_event=STRAIGHT_FEED line=-1 x=100 y=0 z=${g72Fixture.finalZ}`,
|
|
"canon_event=PROGRAM_END",
|
|
"absent=program seem to be stuck",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
const g71Fixtures = [
|
|
{
|
|
name: "endless_loop",
|
|
dir: "g71-endless-loop",
|
|
file: "g71-endless-loop.ngc",
|
|
readCount: 19,
|
|
executeCount: 19,
|
|
keyEvents: [
|
|
"canon_event=STRAIGHT_TRAVERSE line=17 x=15 y=0 z=0",
|
|
"canon_event=ARC_FEED line=-1 first_end=-14.04 second_end=10",
|
|
"canon_event=STRAIGHT_FEED line=-1 x=15 y=0 z=-21.96",
|
|
],
|
|
},
|
|
{
|
|
name: "endless_loop2",
|
|
dir: "g71-endless-loop2",
|
|
file: "g71-endless-loop2.ngc",
|
|
readCount: 12,
|
|
executeCount: 12,
|
|
keyEvents: [
|
|
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
|
|
"canon_event=STRAIGHT_TRAVERSE line=9 x=168 y=0 z=0",
|
|
"canon_event=STRAIGHT_FEED line=-1 x=170 y=0 z=-415",
|
|
"canon_event=STRAIGHT_TRAVERSE line=11 x=300 y=0 z=10",
|
|
],
|
|
},
|
|
{
|
|
name: "endless_loop_2",
|
|
dir: "g71-endless-loop_2",
|
|
file: "g71-endless-loop_2.ngc",
|
|
readCount: 40,
|
|
executeCount: 40,
|
|
keyEvents: [
|
|
"canon_event=COMMENT: interpreter: Lathe diameter mode changed to diameter",
|
|
"canon_event=STRAIGHT_TRAVERSE line=31 x=10 y=0 z=2.8",
|
|
"canon_event=ARC_FEED line=-1 first_end=-27 second_end=10.113",
|
|
"canon_event=COMMENT: interpreter: cutter radius compensation off",
|
|
"canon_event=STRAIGHT_TRAVERSE line=38 x=12.5 y=0 z=-25",
|
|
],
|
|
},
|
|
{
|
|
name: "with_g70",
|
|
dir: "g71-with-g70",
|
|
file: "g71-with-g70.ngc",
|
|
readCount: 32,
|
|
executeCount: 32,
|
|
keyEvents: [
|
|
"canon_event=ARC_FEED line=-1 first_end=1.62661 second_end=1.78761",
|
|
"canon_event=ARC_FEED line=-1 first_end=-36.1 second_end=13",
|
|
"canon_event=STRAIGHT_FEED line=-1 x=15 y=0 z=-38.1",
|
|
],
|
|
},
|
|
];
|
|
if (g71Fixtures.length !== 4 || new Set(g71Fixtures.map((fixture) => `${fixture.dir}/${fixture.file}`)).size !== g71Fixtures.length) {
|
|
throw new Error("browser_interp_g71_fixture_coverage: list drift");
|
|
}
|
|
for (const g71Fixture of g71Fixtures) {
|
|
const g71Path = await writeInterpRegressionFile(interp, g71Fixture.dir, g71Fixture.file);
|
|
assertInterpRegressionFileStaging(g71Fixture.dir, g71Fixture.file, g71Path);
|
|
verifyExpectedOutput(
|
|
`browser_interp_g71_${g71Fixture.name}`,
|
|
interp.runFile(g71Path),
|
|
[
|
|
"file_open=0",
|
|
`file_read_count=${g71Fixture.readCount}`,
|
|
`file_execute_count=${g71Fixture.executeCount}`,
|
|
"file_saw_error=0",
|
|
...g71Fixture.keyEvents,
|
|
"canon_event=PROGRAM_END",
|
|
"absent=program seem to be stuck",
|
|
"absent=killing",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
const g76Files = [
|
|
"g76only.ngc",
|
|
"test.tbl",
|
|
];
|
|
const g76Dir = await writeInterpRegressionFiles(interp, "g76", g76Files);
|
|
assertInterpRegressionFilesStaging("g76", g76Files, g76Dir);
|
|
interp.writeTextFile(
|
|
`${g76Dir}/test.ini`,
|
|
[
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = test.tbl",
|
|
"[TRAJ]",
|
|
"COORDINATES = X Y Z A B C U V W",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g76",
|
|
interp.runFileWithIni(`${g76Dir}/g76only.ngc`, `${g76Dir}/test.ini`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=30",
|
|
"file_execute_count=30",
|
|
"file_saw_error=0",
|
|
"run_step phase=execute step=6 rc=2 line=6",
|
|
"statement_uri=t4%20m6",
|
|
"run_step phase=execute step=25 rc=0 line=25 x=0.2 y=0 z=-0.5",
|
|
"canon_event=SELECT_TOOL tool=4",
|
|
"canon_event=CHANGE_TOOL",
|
|
"canon_event=START_SPINDLE_CLOCKWISE spindle=0",
|
|
"canon_event=START_SPEED_FEED_SYNCH spindle=0 feed_per_revolution=0.05 velocity_mode=0",
|
|
"canon_event=START_SPEED_FEED_SYNCH spindle=0 feed_per_revolution=0.0672681 velocity_mode=0",
|
|
"canon_event=STOP_SPEED_FEED_SYNCH",
|
|
"canon_event=STRAIGHT_TRAVERSE line=25 x=0.237 y=0 z=0.195474",
|
|
"canon_event=STRAIGHT_TRAVERSE line=26 x=0.5 y=0 z=-0.5",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=Requested tool 4 not found",
|
|
].join("\n"),
|
|
);
|
|
|
|
const rotationAbsPtsPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"rotation/abs-pts",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging("rotation/abs-pts", "test.ngc", rotationAbsPtsPath);
|
|
const rotationRegressionFiles = [
|
|
"abs-pts/test.ngc",
|
|
"g28/g28.ngc",
|
|
"g53/g53.ngc",
|
|
];
|
|
if (rotationRegressionFiles.length !== 3 || new Set(rotationRegressionFiles).size !== rotationRegressionFiles.length) {
|
|
throw new Error("browser_interp_rotation_fixture_coverage: list drift");
|
|
}
|
|
const rotationAbsPtsOutput = interp.runFile(rotationAbsPtsPath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_rotation_abs_pts",
|
|
rotationAbsPtsOutput,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=192",
|
|
"file_execute_count=192",
|
|
"file_saw_error=0",
|
|
"canon_event=MESSAGE: 0.000000 0.000000 0.000000",
|
|
"canon_event=MESSAGE: 1.000000 2.000000 3.000000",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
if (
|
|
(rotationAbsPtsOutput.match(/canon_event=MESSAGE: 0\.000000 0\.000000 0\.000000/g) || [])
|
|
.length !== 14
|
|
) {
|
|
throw new Error("browser_interp_rotation_abs_pts: zero absolute-position message count");
|
|
}
|
|
if (
|
|
(rotationAbsPtsOutput.match(/canon_event=MESSAGE: 1\.000000 2\.000000 3\.000000/g) || [])
|
|
.length !== 14
|
|
) {
|
|
throw new Error("browser_interp_rotation_abs_pts: target absolute-position message count");
|
|
}
|
|
|
|
const rotationG28Path = await writeInterpRegressionFile(interp, "rotation/g28", "g28.ngc");
|
|
assertInterpRegressionFileStaging("rotation/g28", "g28.ngc", rotationG28Path);
|
|
const rotationG28Output = interp.runFile(rotationG28Path);
|
|
verifyExpectedOutput(
|
|
"browser_interp_rotation_g28",
|
|
rotationG28Output,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=13",
|
|
"file_execute_count=13",
|
|
"file_saw_error=0",
|
|
"canon_event=SET_XY_ROTATION rotation=45",
|
|
"canon_event=COMMENT: G55 G28",
|
|
"canon_event=STRAIGHT_TRAVERSE line=9 x=10 y=10 z=0",
|
|
"canon_event=COMMENT: G56 G28",
|
|
"canon_event=STRAIGHT_TRAVERSE line=14 x=0 y=14.1421 z=0 a=1",
|
|
"canon_event=FINISH",
|
|
].join("\n"),
|
|
);
|
|
if (!/canon_event=STRAIGHT_TRAVERSE line=14 x=(0|[0-9.e-]+) y=14\.1421 z=0 a=0/.test(rotationG28Output)) {
|
|
throw new Error("browser_interp_rotation_g28: G56 G28 final machine point");
|
|
}
|
|
|
|
const rotationG53Path = await writeInterpRegressionFile(interp, "rotation/g53", "g53.ngc");
|
|
assertInterpRegressionFileStaging("rotation/g53", "g53.ngc", rotationG53Path);
|
|
const rotationG53Output = interp.runFile(rotationG53Path);
|
|
verifyExpectedOutput(
|
|
"browser_interp_rotation_g53",
|
|
rotationG53Output,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=17",
|
|
"file_execute_count=17",
|
|
"file_saw_error=0",
|
|
"canon_event=SET_XY_ROTATION rotation=45",
|
|
"canon_event=COMMENT: g53 + g55 to 1,1",
|
|
"canon_event=COMMENT: g53 + g55 + g92 to 1,1",
|
|
"canon_event=STRAIGHT_TRAVERSE line=13 x=0 y=0 z=0",
|
|
"canon_event=COMMENT: g53 + g56 + g92 to 1,1",
|
|
"canon_event=STRAIGHT_TRAVERSE line=18 x=-0.414214 y=1 z=0 a=1",
|
|
"canon_event=FINISH",
|
|
].join("\n"),
|
|
);
|
|
if (!/canon_event=STRAIGHT_TRAVERSE line=8 x=1\.41421 y=([0-9.e-]+) z=0/.test(rotationG53Output)) {
|
|
throw new Error("browser_interp_rotation_g53: rotated G53 endpoint");
|
|
}
|
|
|
|
const iniparamPlan = await stageInterpIniContext(interp, "iniparam");
|
|
const iniparamNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (iniparamNgcFiles.length !== 1 || new Set(iniparamNgcFiles).size !== iniparamNgcFiles.length) {
|
|
throw new Error("browser_interp_iniparam_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging("iniparam", iniparamNgcFiles, iniparamPlan);
|
|
verifyExpectedOutput(
|
|
"browser_interp_iniparam",
|
|
interp.runFileWithIniContinueOnError(
|
|
iniparamPlan.programPath,
|
|
iniparamPlan.iniPath,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=9",
|
|
"file_execute_count=8",
|
|
"file_saw_error=1",
|
|
"file_error_text=Named parameter #<_ini[nosuchsection]nosuchname> not defined",
|
|
"setup.current_x=10",
|
|
"setup.current_y=20",
|
|
"setup.current_z=30",
|
|
"canon_event=STRAIGHT_TRAVERSE line=1 x=10 y=20 z=30",
|
|
"canon_event=MESSAGE: position now: 10.000000 20.000000 30.000000",
|
|
"canon_event=MESSAGE: not in INI: ######",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const iniparamFailassignPlan = await stageInterpIniContext(
|
|
interp,
|
|
"iniparam-failassign",
|
|
);
|
|
const iniparamFailassignNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (iniparamFailassignNgcFiles.length !== 1 || new Set(iniparamFailassignNgcFiles).size !== iniparamFailassignNgcFiles.length) {
|
|
throw new Error("browser_interp_iniparam_failassign_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging(
|
|
"iniparam-failassign",
|
|
iniparamFailassignNgcFiles,
|
|
iniparamFailassignPlan,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_iniparam_failassign",
|
|
interp.runFileWithIni(
|
|
iniparamFailassignPlan.programPath,
|
|
iniparamFailassignPlan.iniPath,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_execute_1=5",
|
|
"file_read_count=1",
|
|
"file_execute_count=1",
|
|
"file_saw_error=1",
|
|
"file_error_text=Cannot assign to read-only parameter #<_ini[vars]toolchange_x>",
|
|
"absent=canon_event=MESSAGE: notreached",
|
|
].join("\n"),
|
|
);
|
|
|
|
const subCallFromSubPlan = await stageInterpIniContext(interp, "sub-call-from-sub");
|
|
const subCallFromSubNgcFiles = [
|
|
"subs/caller.ngc",
|
|
"subs/helper.ngc",
|
|
"test.ngc",
|
|
];
|
|
if (subCallFromSubNgcFiles.length !== 3 || new Set(subCallFromSubNgcFiles).size !== subCallFromSubNgcFiles.length) {
|
|
throw new Error("browser_interp_sub_call_from_sub_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging(
|
|
"sub-call-from-sub",
|
|
subCallFromSubNgcFiles,
|
|
subCallFromSubPlan,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_sub_call_from_sub",
|
|
interp.runFileWithIni(
|
|
subCallFromSubPlan.programPath,
|
|
subCallFromSubPlan.iniPath,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=10",
|
|
"file_execute_count=10",
|
|
"canon_event=COMMENT: Test: calling a sub from within another sub is valid",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const sequenceNumberPlan = await stageInterpIniContext(interp, "sequence-number");
|
|
const sequenceNumberNgcFiles = [
|
|
"rm400.ngc",
|
|
"test.ngc",
|
|
];
|
|
if (sequenceNumberNgcFiles.length !== 2 || new Set(sequenceNumberNgcFiles).size !== sequenceNumberNgcFiles.length) {
|
|
throw new Error("browser_interp_sequence_number_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging(
|
|
"sequence-number",
|
|
sequenceNumberNgcFiles,
|
|
sequenceNumberPlan,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_sequence_number",
|
|
interp.runFileWithIni(
|
|
sequenceNumberPlan.programPath,
|
|
sequenceNumberPlan.iniPath,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=13",
|
|
"file_execute_count=13",
|
|
"canon_event=MESSAGE: main: line=7.000000 - expect 7",
|
|
"canon_event=MESSAGE: in rm400.ngc line=2.000000 - expect 2",
|
|
"canon_event=MESSAGE: main: line=9.000000 - expect 9",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const nestedSubErrorPlan = await stageInterpIniContext(interp, "nested-sub-error");
|
|
const nestedSubErrorNgcFiles = [
|
|
"subs/nested.ngc",
|
|
"test.ngc",
|
|
];
|
|
if (nestedSubErrorNgcFiles.length !== 2 || new Set(nestedSubErrorNgcFiles).size !== nestedSubErrorNgcFiles.length) {
|
|
throw new Error("browser_interp_nested_sub_error_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging(
|
|
"nested-sub-error",
|
|
nestedSubErrorNgcFiles,
|
|
nestedSubErrorPlan,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_nested_sub_error",
|
|
interp.runFileWithIni(
|
|
nestedSubErrorPlan.programPath,
|
|
nestedSubErrorPlan.iniPath,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_4=5",
|
|
"file_error_text=Nested subroutine definition: 'O100 sub' found inside called subroutine 'Onested'",
|
|
"file_read_count=4",
|
|
"file_execute_count=3",
|
|
"canon_event=COMMENT: Test: nested sub definition inside named sub should error",
|
|
"absent=canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const nestedSubInFileErrorPlan = await stageInterpIniContext(
|
|
interp,
|
|
"nested-sub-in-file-error",
|
|
);
|
|
const nestedSubInFileErrorNgcFiles = [
|
|
"subs/sequential.ngc",
|
|
"test.ngc",
|
|
];
|
|
if (nestedSubInFileErrorNgcFiles.length !== 2 || new Set(nestedSubInFileErrorNgcFiles).size !== nestedSubInFileErrorNgcFiles.length) {
|
|
throw new Error("browser_interp_nested_sub_in_file_error_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging(
|
|
"nested-sub-in-file-error",
|
|
nestedSubInFileErrorNgcFiles,
|
|
nestedSubInFileErrorPlan,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_nested_sub_in_file_error",
|
|
interp.runFileWithIni(
|
|
nestedSubInFileErrorPlan.programPath,
|
|
nestedSubInFileErrorPlan.iniPath,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_execute_5=5",
|
|
"file_error_text=Subroutine 'O200' not found -- not in offset table and no file '",
|
|
"file_read_count=5",
|
|
"file_execute_count=5",
|
|
"canon_event=COMMENT: Test: numbered sub after named endsub in same file should error",
|
|
"canon_event=MESSAGE: sequential main: 7.000000 8.000000 9.000000",
|
|
"absent=canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const owordUnwindPlan = await stageInterpIniContext(interp, "oword-unwind");
|
|
const owordUnwindNgcFiles = [
|
|
"fail.ngc",
|
|
"test.ngc",
|
|
];
|
|
if (owordUnwindNgcFiles.length !== 2 || new Set(owordUnwindNgcFiles).size !== owordUnwindNgcFiles.length) {
|
|
throw new Error("browser_interp_oword_unwind_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging(
|
|
"oword-unwind",
|
|
owordUnwindNgcFiles,
|
|
owordUnwindPlan,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_oword_unwind",
|
|
interp.runFileWithIniContinueOnError(
|
|
owordUnwindPlan.programPath,
|
|
owordUnwindPlan.iniPath,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_5=5",
|
|
"file_read_10=5",
|
|
"file_read_count=11",
|
|
"file_execute_count=9",
|
|
"file_saw_error=1",
|
|
"canon_event=MESSAGE: pre divide-by-zero",
|
|
"canon_event=PROGRAM_END",
|
|
"absent=canon_event=MESSAGE: post divide-by-zero",
|
|
].join("\n"),
|
|
);
|
|
|
|
const abortHotCommentPlan = await stageInterpIniContext(interp, "abort-hot-comment");
|
|
const abortHotCommentNgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (abortHotCommentNgcFiles.length !== 1 || new Set(abortHotCommentNgcFiles).size !== abortHotCommentNgcFiles.length) {
|
|
throw new Error("browser_interp_abort_hot_comment_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging(
|
|
"abort-hot-comment",
|
|
abortHotCommentNgcFiles,
|
|
abortHotCommentPlan,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_abort_hot_comment",
|
|
interp.runFileWithIniContinueOnError(
|
|
abortHotCommentPlan.programPath,
|
|
abortHotCommentPlan.iniPath,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=125",
|
|
"file_execute_count=125",
|
|
"file_saw_error=1",
|
|
"file_error_text= MixedCase param42=20.000000 named=4711.000000 INI=3.140000",
|
|
"absent=canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m19Plan = await stageInterpIniContext(interp, "m19");
|
|
const m19NgcFiles = [
|
|
"test.ngc",
|
|
];
|
|
if (m19NgcFiles.length !== 1 || new Set(m19NgcFiles).size !== m19NgcFiles.length) {
|
|
throw new Error("browser_interp_m19_fixture_coverage: list drift");
|
|
}
|
|
assertInterpIniContextNgcStaging(
|
|
"m19",
|
|
m19NgcFiles,
|
|
m19Plan,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m19",
|
|
interp.runFileWithIni(m19Plan.programPath, m19Plan.iniPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=8",
|
|
"file_execute_count=8",
|
|
"file_saw_error=0",
|
|
"canon_event=ORIENT_SPINDLE spindle=0 orientation=87 mode=0",
|
|
"canon_event=WAIT_SPINDLE_ORIENT_COMPLETE spindle=0 timeout=2",
|
|
"canon_event=ORIENT_SPINDLE spindle=0 orientation=42 mode=1",
|
|
"canon_event=ORIENT_SPINDLE spindle=0 orientation=132 mode=0",
|
|
"canon_event=ORIENT_SPINDLE spindle=0 orientation=132 mode=1",
|
|
"canon_event=WAIT_SPINDLE_ORIENT_COMPLETE spindle=0 timeout=1",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const magicCommentsParamFormatPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"magic_comments/param_format_printing",
|
|
"test.ngc",
|
|
);
|
|
const magicCommentsRegressionFiles = [
|
|
"param_format_printing/test.ngc",
|
|
];
|
|
if (magicCommentsRegressionFiles.length !== 1 || new Set(magicCommentsRegressionFiles).size !== magicCommentsRegressionFiles.length) {
|
|
throw new Error("browser_interp_magic_comments_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging(
|
|
"magic_comments/param_format_printing",
|
|
"test.ngc",
|
|
magicCommentsParamFormatPath,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_magic_comments_param_format",
|
|
interp.runFile(magicCommentsParamFormatPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=13",
|
|
"file_execute_count=13",
|
|
"file_saw_error=0",
|
|
"canon_event=MESSAGE: native value = 1.123457",
|
|
"canon_event=MESSAGE: Test round to integer: 1",
|
|
"canon_event=MESSAGE: Test round to 4 decimals: 1.1235",
|
|
"canon_event=MESSAGE: Test format separate from param: The value is: 1.1235",
|
|
"canon_event=MESSAGE: Test round to 7 decimals: 1.1234568",
|
|
"canon_event=MESSAGE: native value2 = 2.345679",
|
|
"canon_event=MESSAGE: Test2 round all params in line to 4 decimals: The values are: 1.1235, 2.3457",
|
|
"canon_event=MESSAGE: Test2 only round last value to integer: The values are: 1.123457, 2",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99RegressionFiles = [
|
|
"01-basics/test.ngc",
|
|
"02-variables/test.ngc",
|
|
"03-error-M98-no-P-word/test.ngc",
|
|
"04-M98-but-no-sub/test.ngc",
|
|
"05-M98-loops/test.ngc",
|
|
"06-error-mixed-sub-styles/O...-called-with-O..._call.ngc",
|
|
"06-error-mixed-sub-styles/O...-ended-with-O..._endsub.ngc",
|
|
"06-error-mixed-sub-styles/O...-sub-called-with-M98.ngc",
|
|
"06-error-mixed-sub-styles/O...-sub-ended-with-M99.ngc",
|
|
"07-nested-subs/test.ngc",
|
|
"08-sub-follows-main/test.ngc",
|
|
"09-disable-fanuc-subs/test-fanuc.ngc",
|
|
"09-disable-fanuc-subs/test-rs274ngc.ngc",
|
|
"10-M98-P001/test.ngc",
|
|
"11-main-program-oword/test-illegal-end-main-with-eof.ngc",
|
|
"11-main-program-oword/test-illegal-no-m30-before-osub.ngc",
|
|
"11-main-program-oword/test-illegal-sub-after-percent.ngc",
|
|
"11-main-program-oword/test-legal-end-main-with-m02.ngc",
|
|
"11-main-program-oword/test-legal-end-main-with-m2.ngc",
|
|
"11-main-program-oword/test-legal-end-main-with-m30.ngc",
|
|
"11-main-program-oword/test-legal-end-main-with-percent.ngc",
|
|
"13-named-program/test-named.ngc",
|
|
"13-named-program/test-numbered.ngc",
|
|
"14-o-expression-call/test.ngc",
|
|
];
|
|
if (m98m99RegressionFiles.length !== 24 || new Set(m98m99RegressionFiles).size !== m98m99RegressionFiles.length) {
|
|
throw new Error("browser_interp_m98m99_fixture_coverage: list drift");
|
|
}
|
|
|
|
const m98m99BasicsPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/01-basics",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging("m98m99/01-basics", "test.ngc", m98m99BasicsPath);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_01_basics",
|
|
interp.runFile(m98m99BasicsPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=22",
|
|
"file_execute_count=22",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: A simple O sub example ",
|
|
"canon_event=STRAIGHT_TRAVERSE line=10 x=3 y=0 z=0.25",
|
|
"canon_event=STRAIGHT_FEED line=11 x=3 y=0 z=-1",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99VariablesPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/02-variables",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging("m98m99/02-variables", "test.ngc", m98m99VariablesPath);
|
|
const m98m99VariablesRun = runWithCapturedPrints(() =>
|
|
interp.runFile(m98m99VariablesPath),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_02_variables",
|
|
m98m99VariablesRun.output,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=54",
|
|
"file_execute_count=54",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: Fanuc params have global scope; rs274ngc params #1..#30 have local scope ",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_02_variables_prints",
|
|
m98m99VariablesRun.prints,
|
|
[
|
|
"Q MAIN/FANUC: POST-M98: 1=13.010000; 30=13.300000; 31=13.310000",
|
|
"Q MAIN/RS274NGC: POST-O-CALL: 1=42.010000; 30=42.300000; 31=13.310000",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99M98NoPWordPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/03-error-M98-no-P-word",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging(
|
|
"m98m99/03-error-M98-no-P-word",
|
|
"test.ngc",
|
|
m98m99M98NoPWordPath,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_03_error_m98_no_p_word",
|
|
interp.runFile(m98m99M98NoPWordPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=3",
|
|
"file_execute_count=2",
|
|
"file_saw_error=1",
|
|
"file_error_text=Found 'm98' code with no P-word",
|
|
"run_step phase=read step=3 rc=5 line=4",
|
|
"statement_uri=M98",
|
|
"canon_event=COMMENT: Fanuc M98 requires P-word ",
|
|
"absent=canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99M98ButNoSubPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/04-M98-but-no-sub",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging(
|
|
"m98m99/04-M98-but-no-sub",
|
|
"test.ngc",
|
|
m98m99M98ButNoSubPath,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_04_m98_but_no_sub",
|
|
interp.runFile(m98m99M98ButNoSubPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=5",
|
|
"file_execute_count=4",
|
|
"file_saw_error=1",
|
|
"file_error_text=Failed to find sub 'O1' before EOF",
|
|
"run_step phase=read step=5 rc=5 line=4",
|
|
"statement_uri=%25",
|
|
"canon_event=COMMENT: M98 call non-existant sub ",
|
|
"absent=canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99M98LoopsPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/05-M98-loops",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging("m98m99/05-M98-loops", "test.ngc", m98m99M98LoopsPath);
|
|
const m98m99M98LoopsRun = runWithCapturedPrints(() =>
|
|
interp.runFile(m98m99M98LoopsPath),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_05_m98_loops",
|
|
m98m99M98LoopsRun.output,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=102",
|
|
"file_execute_count=102",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: A simple O sub example ",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_05_m98_loops_prints",
|
|
m98m99M98LoopsRun.prints,
|
|
[
|
|
"X SUB O1 PARAM1 = 10.000000",
|
|
"X MAIN 10LOOP PARAM1 = 10.000000",
|
|
"X MAIN 0LOOP PARAM1 = 0.000000",
|
|
"X MAIN 2LOOP PARAM1 = 2.000000",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99MixedSubStyleFiles = [
|
|
"O...-called-with-O..._call.ngc",
|
|
"O...-ended-with-O..._endsub.ngc",
|
|
"O...-sub-called-with-M98.ngc",
|
|
"O...-sub-ended-with-M99.ngc",
|
|
];
|
|
const m98m99MixedSubStyleDir = await writeInterpRegressionFiles(
|
|
interp,
|
|
"m98m99/06-error-mixed-sub-styles",
|
|
m98m99MixedSubStyleFiles,
|
|
);
|
|
assertInterpRegressionFilesStaging(
|
|
"m98m99/06-error-mixed-sub-styles",
|
|
m98m99MixedSubStyleFiles,
|
|
m98m99MixedSubStyleDir,
|
|
);
|
|
for (const mixedSubStyleFixture of [
|
|
{
|
|
name: "called_with_o_call",
|
|
file: "O...-called-with-O..._call.ngc",
|
|
readCount: 5,
|
|
executeCount: 5,
|
|
errorText: "Fanuc 'O....' subroutine must be called with 'M98'",
|
|
errorStep: "run_step phase=execute step=5 rc=5 line=4",
|
|
statementUri: "statement_uri=O1",
|
|
comment: "canon_event=COMMENT: Fanuc 'O...' sub must be called with M98 ",
|
|
},
|
|
{
|
|
name: "ended_with_o_endsub",
|
|
file: "O...-ended-with-O..._endsub.ngc",
|
|
readCount: 7,
|
|
executeCount: 7,
|
|
errorText: "Fanuc 'O....' subroutine definition must end with 'M99'",
|
|
errorStep: "run_step phase=execute step=7 rc=5 line=4",
|
|
statementUri: "statement_uri=O1%20endsub",
|
|
comment: "canon_event=COMMENT: Fanuc 'O...' sub must end with M99 ",
|
|
print: "X FANUC",
|
|
},
|
|
{
|
|
name: "sub_called_with_m98",
|
|
file: "O...-sub-called-with-M98.ngc",
|
|
readCount: 5,
|
|
executeCount: 5,
|
|
errorText: "'O.... sub' subroutine must be called with 'O.... call'",
|
|
errorStep: "run_step phase=execute step=5 rc=5 line=4",
|
|
statementUri: "statement_uri=O1%20sub",
|
|
comment: "canon_event=COMMENT: RS274NGC 'O...' sub must not be called with M98 ",
|
|
},
|
|
{
|
|
name: "sub_ended_with_m99",
|
|
file: "O...-sub-ended-with-M99.ngc",
|
|
readCount: 7,
|
|
executeCount: 7,
|
|
errorText: "'O.... endsub' or 'O.... return' must follow 'O.... sub' subroutine definition",
|
|
errorStep: "run_step phase=execute step=7 rc=5 line=4",
|
|
statementUri: "statement_uri=M99",
|
|
comment: "canon_event=COMMENT: RS274NGC 'O... sub' sub must not end with M99 ",
|
|
print: "X FANUC",
|
|
},
|
|
]) {
|
|
const mixedSubStylePath = `${m98m99MixedSubStyleDir}/${mixedSubStyleFixture.file}`;
|
|
const mixedSubStyleRun = runWithCapturedPrints(() =>
|
|
interp.runFile(mixedSubStylePath),
|
|
);
|
|
verifyExpectedOutput(
|
|
`browser_interp_m98m99_06_${mixedSubStyleFixture.name}`,
|
|
mixedSubStyleRun.output,
|
|
[
|
|
"file_open=0",
|
|
`file_read_count=${mixedSubStyleFixture.readCount}`,
|
|
`file_execute_count=${mixedSubStyleFixture.executeCount}`,
|
|
"file_saw_error=1",
|
|
`file_error_text=${mixedSubStyleFixture.errorText}`,
|
|
mixedSubStyleFixture.errorStep,
|
|
mixedSubStyleFixture.statementUri,
|
|
mixedSubStyleFixture.comment,
|
|
"absent=canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
if (mixedSubStyleFixture.print) {
|
|
verifyExpectedOutput(
|
|
`browser_interp_m98m99_06_${mixedSubStyleFixture.name}_prints`,
|
|
mixedSubStyleRun.prints,
|
|
mixedSubStyleFixture.print,
|
|
);
|
|
}
|
|
}
|
|
|
|
const m98m99NestedSubsPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/07-nested-subs",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging("m98m99/07-nested-subs", "test.ngc", m98m99NestedSubsPath);
|
|
const m98m99NestedSubsRun = runWithCapturedPrints(() =>
|
|
interp.runFile(m98m99NestedSubsPath),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_07_nested_subs",
|
|
m98m99NestedSubsRun.output,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=163",
|
|
"file_execute_count=163",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: A nested Fanuc subroutine example ",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_07_nested_subs_prints",
|
|
m98m99NestedSubsRun.prints,
|
|
[
|
|
"X >>>> LOOP [O2.O1]: 4.500000",
|
|
"X MAIN END: 1=5.000000",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99SubFollowsMainPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/08-sub-follows-main",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging(
|
|
"m98m99/08-sub-follows-main",
|
|
"test.ngc",
|
|
m98m99SubFollowsMainPath,
|
|
);
|
|
const m98m99SubFollowsMainRun = runWithCapturedPrints(() =>
|
|
interp.runFile(m98m99SubFollowsMainPath),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_08_sub_follows_main",
|
|
m98m99SubFollowsMainRun.output,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=9",
|
|
"file_execute_count=9",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: Fanuc-style subs may follow main program ",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_08_sub_follows_main_prints",
|
|
m98m99SubFollowsMainRun.prints,
|
|
"X IN O1",
|
|
);
|
|
|
|
const m98m99DisableFanucSubsFiles = [
|
|
"test-fanuc.ini",
|
|
"test-no-fanuc.ini",
|
|
"test-fanuc.ngc",
|
|
"test-rs274ngc.ngc",
|
|
];
|
|
const m98m99DisableFanucSubsDir = await writeInterpRegressionFiles(
|
|
interp,
|
|
"m98m99/09-disable-fanuc-subs",
|
|
m98m99DisableFanucSubsFiles,
|
|
);
|
|
assertInterpRegressionFilesStaging(
|
|
"m98m99/09-disable-fanuc-subs",
|
|
m98m99DisableFanucSubsFiles,
|
|
m98m99DisableFanucSubsDir,
|
|
);
|
|
for (const disableFanucCase of [
|
|
{
|
|
name: "fanuc_ini",
|
|
program: "test-fanuc.ngc",
|
|
ini: "test-fanuc.ini",
|
|
readCount: 22,
|
|
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=10 x=3 y=0 z=0.25",
|
|
keyFeed: "canon_event=STRAIGHT_FEED line=11 x=3 y=0 z=-1",
|
|
},
|
|
{
|
|
name: "rs274ngc_ini",
|
|
program: "test-rs274ngc.ngc",
|
|
ini: "test-fanuc.ini",
|
|
readCount: 15,
|
|
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=5 x=1 y=0 z=0.25",
|
|
keyFeed: "canon_event=STRAIGHT_FEED line=6 x=1 y=0 z=-1",
|
|
},
|
|
{
|
|
name: "rs274ngc_no_fanuc",
|
|
program: "test-rs274ngc.ngc",
|
|
ini: "test-no-fanuc.ini",
|
|
readCount: 15,
|
|
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=5 x=1 y=0 z=0.25",
|
|
keyFeed: "canon_event=STRAIGHT_FEED line=6 x=1 y=0 z=-1",
|
|
},
|
|
{
|
|
name: "fanuc_default",
|
|
program: "test-fanuc.ngc",
|
|
readCount: 22,
|
|
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=10 x=3 y=0 z=0.25",
|
|
keyFeed: "canon_event=STRAIGHT_FEED line=11 x=3 y=0 z=-1",
|
|
},
|
|
{
|
|
name: "rs274ngc_default",
|
|
program: "test-rs274ngc.ngc",
|
|
readCount: 15,
|
|
keyTraverse: "canon_event=STRAIGHT_TRAVERSE line=5 x=1 y=0 z=0.25",
|
|
keyFeed: "canon_event=STRAIGHT_FEED line=6 x=1 y=0 z=-1",
|
|
},
|
|
]) {
|
|
const output = disableFanucCase.ini
|
|
? interp.runFileWithIni(
|
|
`${m98m99DisableFanucSubsDir}/${disableFanucCase.program}`,
|
|
`${m98m99DisableFanucSubsDir}/${disableFanucCase.ini}`,
|
|
)
|
|
: interp.runFile(`${m98m99DisableFanucSubsDir}/${disableFanucCase.program}`);
|
|
verifyExpectedOutput(
|
|
`browser_interp_m98m99_09_${disableFanucCase.name}`,
|
|
output,
|
|
[
|
|
"file_open=0",
|
|
`file_read_count=${disableFanucCase.readCount}`,
|
|
`file_execute_count=${disableFanucCase.readCount}`,
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: A simple O sub example ",
|
|
disableFanucCase.keyTraverse,
|
|
disableFanucCase.keyFeed,
|
|
"canon_event=PROGRAM_END",
|
|
"absent=file_error_text=DISABLE_FANUC_STYLE_SUB",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_09_fanuc_no_fanuc",
|
|
interp.runFileWithIni(
|
|
`${m98m99DisableFanucSubsDir}/test-fanuc.ngc`,
|
|
`${m98m99DisableFanucSubsDir}/test-no-fanuc.ini`,
|
|
),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=5",
|
|
"file_execute_count=4",
|
|
"file_saw_error=1",
|
|
"file_error_text=DISABLE_FANUC_STYLE_SUB set in INI file, but found m98",
|
|
"run_step phase=read step=5 rc=5 line=6",
|
|
"statement_uri=M98%20P1%20L3",
|
|
"canon_event=STRAIGHT_TRAVERSE line=5 x=0 y=0 z=0.25",
|
|
"absent=canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99M98P001Path = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/10-M98-P001",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging("m98m99/10-M98-P001", "test.ngc", m98m99M98P001Path);
|
|
const m98m99M98P001Run = runWithCapturedPrints(() =>
|
|
interp.runFile(m98m99M98P001Path),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_10_m98_p001",
|
|
m98m99M98P001Run.output,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=8",
|
|
"file_execute_count=8",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: main program",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_10_m98_p001_prints",
|
|
m98m99M98P001Run.prints,
|
|
[
|
|
"x got here",
|
|
"absent=ERROR: should not get here",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99MainProgramOwordFiles = [
|
|
"test-illegal-end-main-with-eof.ngc",
|
|
"test-illegal-no-m30-before-osub.ngc",
|
|
"test-illegal-sub-after-percent.ngc",
|
|
"test-legal-end-main-with-m02.ngc",
|
|
"test-legal-end-main-with-m2.ngc",
|
|
"test-legal-end-main-with-m30.ngc",
|
|
"test-legal-end-main-with-percent.ngc",
|
|
];
|
|
const m98m99MainProgramOwordDir = await writeInterpRegressionFiles(
|
|
interp,
|
|
"m98m99/11-main-program-oword",
|
|
m98m99MainProgramOwordFiles,
|
|
);
|
|
assertInterpRegressionFilesStaging(
|
|
"m98m99/11-main-program-oword",
|
|
m98m99MainProgramOwordFiles,
|
|
m98m99MainProgramOwordDir,
|
|
);
|
|
for (const mainProgramFixture of [
|
|
{
|
|
name: "legal_m2",
|
|
file: "test-legal-end-main-with-m2.ngc",
|
|
readCount: 17,
|
|
firstLine: 4,
|
|
subLine: 13,
|
|
endEvent: "canon_event=PROGRAM_END",
|
|
},
|
|
{
|
|
name: "legal_m02",
|
|
file: "test-legal-end-main-with-m02.ngc",
|
|
readCount: 17,
|
|
firstLine: 4,
|
|
subLine: 13,
|
|
endEvent: "canon_event=PROGRAM_END",
|
|
},
|
|
{
|
|
name: "legal_m30",
|
|
file: "test-legal-end-main-with-m30.ngc",
|
|
readCount: 17,
|
|
firstLine: 4,
|
|
subLine: 13,
|
|
endEvent: "canon_event=PROGRAM_END",
|
|
},
|
|
{
|
|
name: "legal_percent",
|
|
file: "test-legal-end-main-with-percent.ngc",
|
|
readCount: 20,
|
|
firstLine: 11,
|
|
subLine: 6,
|
|
endEvent: "canon_event=FINISH",
|
|
},
|
|
]) {
|
|
verifyExpectedOutput(
|
|
`browser_interp_m98m99_11_${mainProgramFixture.name}`,
|
|
interp.runFile(`${m98m99MainProgramOwordDir}/${mainProgramFixture.file}`),
|
|
[
|
|
"file_open=0",
|
|
`file_read_count=${mainProgramFixture.readCount}`,
|
|
`file_execute_count=${mainProgramFixture.readCount}`,
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: legal example",
|
|
`canon_event=STRAIGHT_TRAVERSE line=${mainProgramFixture.firstLine} x=1 y=2 z=0`,
|
|
`canon_event=STRAIGHT_TRAVERSE line=${mainProgramFixture.subLine} x=1 y=2 z=3`,
|
|
mainProgramFixture.endEvent,
|
|
"absent=should never get here",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
for (const mainProgramErrorFixture of [
|
|
{
|
|
name: "illegal_eof",
|
|
file: "test-illegal-end-main-with-eof.ngc",
|
|
readCount: 21,
|
|
executeCount: 20,
|
|
errorText: "File ended with no percent sign (%) or program end (M2)",
|
|
marker: "canon_event=COMMENT: end of main signaled by EOF",
|
|
},
|
|
{
|
|
name: "illegal_no_m30",
|
|
file: "test-illegal-no-m30-before-osub.ngc",
|
|
readCount: 18,
|
|
executeCount: 18,
|
|
errorText: "File:",
|
|
marker: "statement_uri=O2%20sub%20%28subprogram%20begin%29",
|
|
extraMarker: "sub: o|2| found in illegal location",
|
|
},
|
|
{
|
|
name: "illegal_sub_after_percent",
|
|
file: "test-illegal-sub-after-percent.ngc",
|
|
readCount: 8,
|
|
executeCount: 7,
|
|
errorText: "Failed to find sub 'O2' before EOF",
|
|
marker: "canon_event=FINISH",
|
|
},
|
|
]) {
|
|
verifyExpectedOutput(
|
|
`browser_interp_m98m99_11_${mainProgramErrorFixture.name}`,
|
|
interp.runFile(`${m98m99MainProgramOwordDir}/${mainProgramErrorFixture.file}`),
|
|
[
|
|
"file_open=0",
|
|
`file_read_count=${mainProgramErrorFixture.readCount}`,
|
|
`file_execute_count=${mainProgramErrorFixture.executeCount}`,
|
|
"file_saw_error=1",
|
|
`file_error_text=${mainProgramErrorFixture.errorText}`,
|
|
mainProgramErrorFixture.marker,
|
|
mainProgramErrorFixture.extraMarker ?? "",
|
|
"absent=canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
}
|
|
|
|
const m98m99NamedProgramFiles = [
|
|
"test-named.ngc",
|
|
"test-numbered.ngc",
|
|
];
|
|
const m98m99NamedProgramDir = await writeInterpRegressionFiles(
|
|
interp,
|
|
"m98m99/13-named-program",
|
|
m98m99NamedProgramFiles,
|
|
);
|
|
assertInterpRegressionFilesStaging(
|
|
"m98m99/13-named-program",
|
|
m98m99NamedProgramFiles,
|
|
m98m99NamedProgramDir,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_13_named_program_named",
|
|
interp.runFile(`${m98m99NamedProgramDir}/test-named.ngc`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=4",
|
|
"file_execute_count=4",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: test-named.ngc: Test named programs",
|
|
"canon_event=COMMENT: ...program body",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_13_named_program_numbered",
|
|
interp.runFile(`${m98m99NamedProgramDir}/test-numbered.ngc`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=4",
|
|
"file_execute_count=4",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: test-numbered.ngc: Test numbered programs",
|
|
"canon_event=COMMENT: ...program body",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const m98m99OExpressionCallPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"m98m99/14-o-expression-call",
|
|
"test.ngc",
|
|
);
|
|
assertInterpRegressionFileStaging(
|
|
"m98m99/14-o-expression-call",
|
|
"test.ngc",
|
|
m98m99OExpressionCallPath,
|
|
);
|
|
const m98m99OExpressionCallRun = runWithCapturedPrints(() =>
|
|
interp.runFile(m98m99OExpressionCallPath),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_14_o_expression_call",
|
|
m98m99OExpressionCallRun.output,
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=29",
|
|
"file_execute_count=29",
|
|
"file_saw_error=0",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_m98m99_14_o_expression_call_prints",
|
|
m98m99OExpressionCallRun.prints,
|
|
[
|
|
"In sub 100",
|
|
"In sub 200",
|
|
].join("\n"),
|
|
);
|
|
|
|
const g10RegressionFiles = [
|
|
"g10-l1-l10/test.ngc",
|
|
"g10-l11/test.ngc",
|
|
"g10-l2-while-active/test.ngc",
|
|
"g10-l20-while-active/test.ngc",
|
|
"g10-with-g92/test.ngc",
|
|
];
|
|
if (g10RegressionFiles.length !== 5 || new Set(g10RegressionFiles).size !== g10RegressionFiles.length) {
|
|
throw new Error("browser_interp_g10_fixture_coverage: list drift");
|
|
}
|
|
|
|
const g10L1L10Dir = await writeG10RegressionFiles(interp, "g10-l1-l10", [
|
|
"test.ngc",
|
|
"test.tbl",
|
|
]);
|
|
assertG10RegressionFilesStaging("g10-l1-l10", ["test.ngc", "test.tbl"], g10L1L10Dir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g10_l1_l10",
|
|
interp.runFileWithIni(`${g10L1L10Dir}/test.ngc`, `${g10L1L10Dir}/test.ini`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=115",
|
|
"file_execute_count=115",
|
|
"file_saw_error=0",
|
|
"canon_event=MESSAGE: G10 L1: set tool offsets direct",
|
|
"canon_event=MESSAGE: G10 L10: set tool offsets relative to position + 45 deg. rotation",
|
|
"canon_event=MESSAGE: Model B contract: M6 alone and G10 alone do not change tool offset params",
|
|
"canon_event=MESSAGE: G10 alone does not apply offset, should still be 0 0 0: 0.000000 0.000000 0.000000",
|
|
"canon_event=USE_TOOL_LENGTH_OFFSET x=8 y=6 z=7",
|
|
"canon_event=SET_XY_ROTATION rotation=45",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const g10L11Dir = await writeG10RegressionFiles(interp, "g10-l11", [
|
|
"test.ngc",
|
|
"test.tbl",
|
|
]);
|
|
assertG10RegressionFilesStaging("g10-l11", ["test.ngc", "test.tbl"], g10L11Dir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g10_l11",
|
|
interp.runFileWithIni(`${g10L11Dir}/test.ngc`, `${g10L11Dir}/test.ini`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=41",
|
|
"file_execute_count=41",
|
|
"file_saw_error=0",
|
|
"canon_event=SET_G5X_OFFSET index=1 x=1 y=2 z=-3",
|
|
"canon_event=SET_G92_OFFSET x=-41.1962 y=-46.1962 z=-72",
|
|
"canon_event=USE_TOOL_LENGTH_OFFSET x=0 y=0 z=-4",
|
|
"canon_event=MESSAGE: -101.600000",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const g10L2WhileActiveDir = await writeG10RegressionFiles(
|
|
interp,
|
|
"g10-l2-while-active",
|
|
["test.ngc"],
|
|
);
|
|
assertG10RegressionFilesStaging("g10-l2-while-active", ["test.ngc"], g10L2WhileActiveDir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g10_l2_while_active",
|
|
interp.runFile(`${g10L2WhileActiveDir}/test.ngc`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=24",
|
|
"file_execute_count=24",
|
|
"file_saw_error=0",
|
|
"canon_event=SET_G5X_OFFSET index=1 x=1 y=0 z=0",
|
|
"canon_event=SET_XY_ROTATION rotation=-45",
|
|
"canon_event=MESSAGE: Should be 0 1.414214: 0.000000 1.414214",
|
|
"canon_event=SET_XY_ROTATION rotation=90",
|
|
"canon_event=MESSAGE: Should be 1 1: 1.000000 1.000000",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const g10L20WhileActiveDir = await writeG10RegressionFiles(
|
|
interp,
|
|
"g10-l20-while-active",
|
|
["test.ngc"],
|
|
);
|
|
assertG10RegressionFilesStaging("g10-l20-while-active", ["test.ngc"], g10L20WhileActiveDir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g10_l20_while_active",
|
|
interp.runFile(`${g10L20WhileActiveDir}/test.ngc`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=20",
|
|
"file_execute_count=20",
|
|
"file_saw_error=0",
|
|
"canon_event=SET_XY_ROTATION rotation=45",
|
|
"canon_event=MESSAGE: Should be -1 0: -1.000000 0.000000",
|
|
"canon_event=MESSAGE: Should be 0 -1: 0.000000 -1.000000",
|
|
"canon_event=MESSAGE: Should be 1 1: 1.000000 1.000000",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const g10WithG92Dir = await writeG10RegressionFiles(interp, "g10-with-g92", [
|
|
"test.ngc",
|
|
"test.tbl",
|
|
]);
|
|
assertG10RegressionFilesStaging("g10-with-g92", ["test.ngc", "test.tbl"], g10WithG92Dir);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g10_with_g92",
|
|
interp.runFileWithIni(`${g10WithG92Dir}/test.ngc`, `${g10WithG92Dir}/test.ini`),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=61",
|
|
"file_execute_count=61",
|
|
"file_saw_error=0",
|
|
"canon_event=SET_G5X_OFFSET index=9 x=25 y=26 z=27",
|
|
"canon_event=SET_G92_OFFSET x=-0.1 y=-0.2 z=-10.3",
|
|
"canon_event=MESSAGE: X0.100000 Y0.200000 Z0.300000",
|
|
"canon_event=MESSAGE: X-23.900000 Y-23.800000 Z-23.700000",
|
|
"canon_event=SET_G92_OFFSET x=0 y=0 z=0",
|
|
"canon_event=MESSAGE: X-24.000000 Y-24.000000 Z-34.000000",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const g52G92InteractionPath = await writeInterpRegressionFile(
|
|
interp,
|
|
"g52/g52-g92-interaction",
|
|
"g52-g92-interaction.ngc",
|
|
);
|
|
const g52RegressionFiles = [
|
|
"g52-g92-interaction/g52-g92-interaction.ngc",
|
|
];
|
|
if (g52RegressionFiles.length !== 1 || new Set(g52RegressionFiles).size !== g52RegressionFiles.length) {
|
|
throw new Error("browser_interp_g52_fixture_coverage: list drift");
|
|
}
|
|
assertInterpRegressionFileStaging(
|
|
"g52/g52-g92-interaction",
|
|
"g52-g92-interaction.ngc",
|
|
g52G92InteractionPath,
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g52_g92_interaction",
|
|
interp.runFile(g52G92InteractionPath),
|
|
[
|
|
"file_open=0",
|
|
"file_read_count=39",
|
|
"file_execute_count=39",
|
|
"file_saw_error=0",
|
|
"canon_event=COMMENT: G52 and G92 param #5210 setting behavior",
|
|
"G92 params: 1.000000 -25.400000 -50.800000",
|
|
"G92 params: 0.000000 -25.400000 -50.800000",
|
|
"G92 params: 1.000000 50.800000 76.200000",
|
|
"G92 params: 1.000000 0.000000 0.000000",
|
|
"canon_event=SET_G92_OFFSET x=-1 y=-2 z=0",
|
|
"canon_event=SET_G92_OFFSET x=2 y=3 z=0",
|
|
"canon_event=PALLET_SHUTTLE",
|
|
"canon_event=PROGRAM_END",
|
|
].join("\n"),
|
|
);
|
|
|
|
const g92PersistenceDir = "/work/browser-interp/g52/g92-persistence";
|
|
const g92PersistenceProgramPath = `${g92PersistenceDir}/test.ngc`;
|
|
const g92PersistenceParameterPath = `${g92PersistenceDir}/startup.var`;
|
|
const g92PersistenceIniPath = `${g92PersistenceDir}/persist.ini`;
|
|
const g92PersistenceDisabledIniPath = `${g92PersistenceDir}/disabled.ini`;
|
|
interp.writeTextFile(
|
|
g92PersistenceProgramPath,
|
|
[
|
|
"(PRINT,Startup G92 params: #5210 #5211 #5212 #5213)",
|
|
"M30",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
interp.writeTextFile(
|
|
g92PersistenceParameterPath,
|
|
[
|
|
"5210 1",
|
|
"5211 1.25",
|
|
"5212 2.5",
|
|
"5213 3.75",
|
|
"5220 1",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
interp.writeTextFile(
|
|
g92PersistenceIniPath,
|
|
[
|
|
"[RS274NGC]",
|
|
"PARAMETER_FILE = startup.var",
|
|
"[TRAJ]",
|
|
"COORDINATES = X Y Z",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
interp.writeTextFile(
|
|
g92PersistenceDisabledIniPath,
|
|
[
|
|
"[RS274NGC]",
|
|
"PARAMETER_FILE = startup.var",
|
|
"DISABLE_G92_PERSISTENCE = 1",
|
|
"[TRAJ]",
|
|
"COORDINATES = X Y Z",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g92_persistence_enabled",
|
|
runWithCapturedPrints(() =>
|
|
interp.runFileWithIni(g92PersistenceProgramPath, g92PersistenceIniPath),
|
|
).prints,
|
|
"Startup G92 params: 1.000000 1.250000 2.500000 3.750000",
|
|
);
|
|
verifyExpectedOutput(
|
|
"browser_interp_g92_persistence_disabled",
|
|
runWithCapturedPrints(() =>
|
|
interp.runFileWithIni(g92PersistenceProgramPath, g92PersistenceDisabledIniPath),
|
|
).prints,
|
|
"Startup G92 params: 0.000000 0.000000 0.000000 0.000000",
|
|
);
|
|
|
|
verifyExpectedOutput(
|
|
"restore_parameters_missing_file",
|
|
interp.restoreParameters("/work/browser-missing.var"),
|
|
"restore_parameters=0",
|
|
);
|
|
const outOfOrderParameterFilePath = "/work/browser-out-of-order.var";
|
|
interp.writeTextFile(outOfOrderParameterFilePath, "5220 1\n5161 2\n");
|
|
verifyExpectedOutput(
|
|
"restore_parameters_out_of_order",
|
|
interp.restoreParameters(outOfOrderParameterFilePath),
|
|
[
|
|
"restore_parameters=5",
|
|
"restore_error_text=Parameter file out of order",
|
|
].join("\n"),
|
|
);
|
|
|
|
const missingRequiredParameterFilePath = "/work/browser-missing-required.var";
|
|
interp.writeTextFile(missingRequiredParameterFilePath, "5161 3.5\n5220 1\n");
|
|
verifyExpectedOutput(
|
|
"restore_parameters_missing_required",
|
|
interp.restoreParameters(missingRequiredParameterFilePath),
|
|
[
|
|
"restore_parameters=0",
|
|
"parameter_5161=3.5",
|
|
"parameter_5162=0",
|
|
].join("\n"),
|
|
);
|
|
|
|
const browserParameterFilePath = "/work/browser-direct.var";
|
|
interp.writeTextFile(
|
|
browserParameterFilePath,
|
|
[
|
|
"5161 10.5",
|
|
"5162 20.25",
|
|
"5220 1",
|
|
"5221 2.25",
|
|
"5399 44",
|
|
"<_named_param> 123",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"restore_parameters_direct",
|
|
interp.restoreParameters(browserParameterFilePath),
|
|
[
|
|
"restore_parameters=0",
|
|
"parameter_5161=10.5",
|
|
"parameter_5162=20.25",
|
|
"parameter_5221=2.25",
|
|
"parameter_5399=44",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"save_parameters_direct",
|
|
interp.saveParameters(browserParameterFilePath, {
|
|
5161: 12.34,
|
|
5162: 56.78,
|
|
5220: 1.0,
|
|
5221: 9.87,
|
|
5399: 66.6,
|
|
}),
|
|
[
|
|
"save_parameters=0",
|
|
"parameter_5161=12.34",
|
|
"parameter_5162=56.78",
|
|
"parameter_5221=9.87",
|
|
"parameter_5399=66.6",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"saved_parameters_direct_text",
|
|
interp.readTextFile(browserParameterFilePath),
|
|
[
|
|
"5161\t12.340000",
|
|
"5162\t56.780000",
|
|
"5221\t9.870000",
|
|
"5399\t66.600000",
|
|
"absent=<_named_param>",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"saved_parameters_direct_backup",
|
|
interp.readTextFile(`${browserParameterFilePath}.bak`),
|
|
[
|
|
"5161 10.5",
|
|
"5162 20.25",
|
|
"5221 2.25",
|
|
"5399 44",
|
|
"<_named_param> 123",
|
|
].join("\n"),
|
|
);
|
|
|
|
const browserToolTablePath = "/work/browser-direct-tool.tbl";
|
|
interp.writeTextFile(
|
|
browserToolTablePath,
|
|
[
|
|
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser direct finish tool",
|
|
"T5 P9 X1 Y2 Z3 ;browser direct rough tool",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"load_tool_table_direct",
|
|
interp.loadToolTable(browserToolTablePath),
|
|
[
|
|
"tooldata_load=0",
|
|
"tooldata_last_index=2",
|
|
"tool_1.toolno=2",
|
|
"tool_1.pocketno=7",
|
|
"tool_1.z=3.125",
|
|
"tool_1.diameter=1.5",
|
|
"tool_1.frontangle=12",
|
|
"tool_1.backangle=34",
|
|
"tool_1.orientation=4",
|
|
"tool_1.comment=browser direct finish tool",
|
|
"tool_2.toolno=5",
|
|
"tool_2.pocketno=9",
|
|
"tool_2.comment=browser direct rough tool",
|
|
"tool_index_for_tool_2=1",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"save_tool_table_direct",
|
|
interp.saveToolTable(browserToolTablePath),
|
|
[
|
|
"tooldata_save=0",
|
|
"tool_1.toolno=2",
|
|
"tool_2.toolno=5",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"saved_tool_table_direct_text",
|
|
interp.readTextFile(browserToolTablePath),
|
|
[
|
|
"T2",
|
|
"P7",
|
|
"Z+3.125000",
|
|
"D+1.500000",
|
|
"I+12.000000",
|
|
"J+34.000000",
|
|
"Q4",
|
|
";browser direct finish tool",
|
|
"T5",
|
|
"P9",
|
|
";browser direct rough tool",
|
|
].join("\n"),
|
|
);
|
|
|
|
const browserRandomToolTablePath = "/work/browser-direct-random-tool.tbl";
|
|
interp.writeTextFile(
|
|
browserRandomToolTablePath,
|
|
[
|
|
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser direct random finish tool",
|
|
"T5 P9 X1 Y2 Z3 ;browser direct random rough tool",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"load_tool_table_direct_random",
|
|
interp.loadToolTable(browserRandomToolTablePath, { randomToolChanger: true }),
|
|
[
|
|
"tooldata_random_toolchanger=1",
|
|
"tooldata_load=0",
|
|
"tooldata_last_index=9",
|
|
"tool_pocket_7.toolno=2",
|
|
"tool_pocket_7.pocketno=7",
|
|
"tool_pocket_7.z=3.125",
|
|
"tool_pocket_7.diameter=1.5",
|
|
"tool_pocket_7.frontangle=12",
|
|
"tool_pocket_7.backangle=34",
|
|
"tool_pocket_7.orientation=4",
|
|
"tool_pocket_7.comment=browser direct random finish tool",
|
|
"tool_pocket_9.toolno=5",
|
|
"tool_pocket_9.pocketno=9",
|
|
"tool_pocket_9.comment=browser direct random rough tool",
|
|
"tool_index_for_tool_2=7",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"save_tool_table_direct_random",
|
|
interp.saveToolTable(browserRandomToolTablePath),
|
|
[
|
|
"tooldata_save=0",
|
|
"tool_pocket_7.toolno=2",
|
|
"tool_pocket_9.toolno=5",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"saved_tool_table_direct_random_text",
|
|
interp.readTextFile(browserRandomToolTablePath),
|
|
[
|
|
"T2",
|
|
"P7",
|
|
";browser direct random finish tool",
|
|
"T5",
|
|
"P9",
|
|
";browser direct random rough tool",
|
|
].join("\n"),
|
|
);
|
|
|
|
await saveMachineTextFiles("browser-interp", {
|
|
ini: "[EMC]\nMACHINE = browser-interp\n",
|
|
toolTable: "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser finish tool\n",
|
|
parameters: [
|
|
"5161 10.5",
|
|
"5162 20.25",
|
|
"5220 1",
|
|
"5221 2.25",
|
|
"5399 44",
|
|
"<_named_param> 123",
|
|
"",
|
|
].join("\n"),
|
|
});
|
|
const loadedSession = await loadMachineSessionFromOpfs(
|
|
interp,
|
|
"browser-interp",
|
|
{
|
|
iniWasmPath: "/work/browser-machine.ini",
|
|
parameterWasmPath: "/work/browser-linuxcnc.var",
|
|
toolTableWasmPath: "/work/browser-tool.tbl",
|
|
},
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_restore_parameters",
|
|
loadedSession.parameters.result,
|
|
[
|
|
"restore_parameters=0",
|
|
"parameter_5161=10.5",
|
|
"parameter_5162=20.25",
|
|
"parameter_5221=2.25",
|
|
"parameter_5399=44",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_load_tool_table",
|
|
loadedSession.toolTable.result,
|
|
[
|
|
"tooldata_load=0",
|
|
"tool_1.toolno=2",
|
|
"tool_1.pocketno=7",
|
|
"tool_1.z=3.125",
|
|
"tool_1.diameter=1.5",
|
|
"tool_1.frontangle=12",
|
|
"tool_1.backangle=34",
|
|
"tool_1.orientation=4",
|
|
"tool_1.comment=browser finish tool",
|
|
"tool_index_for_tool_2=1",
|
|
].join("\n"),
|
|
);
|
|
const savedParameters = await saveMachineParametersToOpfs(
|
|
interp,
|
|
"browser-interp",
|
|
{
|
|
5161: 12.34,
|
|
5162: 56.78,
|
|
5220: 1.0,
|
|
5221: 9.87,
|
|
5399: 66.6,
|
|
},
|
|
{ wasmPath: "/work/browser-linuxcnc.var" },
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_save_parameters",
|
|
savedParameters.result,
|
|
[
|
|
"save_parameters=0",
|
|
"parameter_5161=12.34",
|
|
"parameter_5162=56.78",
|
|
"parameter_5221=9.87",
|
|
"parameter_5399=66.6",
|
|
].join("\n"),
|
|
);
|
|
const machineFiles = await loadMachineTextFiles("browser-interp");
|
|
verifyExpectedOutput(
|
|
"opfs_saved_parameter_text",
|
|
machineFiles.parameters,
|
|
[
|
|
"5161\t12.340000",
|
|
"5162\t56.780000",
|
|
"5221\t9.870000",
|
|
"5399\t66.600000",
|
|
"absent=<_named_param>",
|
|
].join("\n"),
|
|
);
|
|
if (
|
|
savedParameters.backupOpfsPath !==
|
|
"linuxcnc/machines/browser-interp/linuxcnc.var.bak"
|
|
) {
|
|
throw new Error(`unexpected parameter backup path: ${savedParameters.backupOpfsPath}`);
|
|
}
|
|
verifyExpectedOutput(
|
|
"opfs_saved_parameter_backup_text",
|
|
await loadTextFile(savedParameters.backupOpfsPath),
|
|
[
|
|
"5161 10.5",
|
|
"5162 20.25",
|
|
"5221 2.25",
|
|
"5399 44",
|
|
"<_named_param> 123",
|
|
].join("\n"),
|
|
);
|
|
const savedToolTable = await saveMachineToolTableToOpfs(
|
|
interp,
|
|
"browser-interp",
|
|
{ wasmPath: "/work/browser-tool.tbl" },
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_save_tool_table",
|
|
savedToolTable.result,
|
|
[
|
|
"tooldata_save=0",
|
|
"tool_1.toolno=2",
|
|
"tool_1.pocketno=7",
|
|
].join("\n"),
|
|
);
|
|
const savedMachineFiles = await loadMachineTextFiles("browser-interp");
|
|
verifyExpectedOutput(
|
|
"opfs_saved_tool_table_text",
|
|
savedMachineFiles.toolTable,
|
|
[
|
|
"T2",
|
|
"P7",
|
|
"Z+3.125000",
|
|
"D+1.500000",
|
|
"I+12.000000",
|
|
"J+34.000000",
|
|
"Q4",
|
|
";browser finish tool",
|
|
].join("\n"),
|
|
);
|
|
|
|
await saveMachineTextFiles("browser-random-toolchanger", {
|
|
ini: [
|
|
"[EMC]",
|
|
"MACHINE = browser-random-toolchanger",
|
|
"",
|
|
"[EMCIO]",
|
|
"RANDOM_TOOLCHANGER = yes",
|
|
"",
|
|
].join("\n"),
|
|
toolTable: [
|
|
"T2 P7 Z3.125 D1.5 I12 J34 Q4 ;browser random finish tool",
|
|
"T5 P9 X1 Y2 Z3 ;browser random rough tool",
|
|
"",
|
|
].join("\n"),
|
|
parameters: [
|
|
"5161 10.5",
|
|
"5162 20.25",
|
|
"5220 1",
|
|
"5221 2.25",
|
|
"5399 44",
|
|
"",
|
|
].join("\n"),
|
|
});
|
|
const loadedRandomSession = await loadMachineSessionFromOpfs(
|
|
interp,
|
|
"browser-random-toolchanger",
|
|
{
|
|
iniSdk: ini,
|
|
iniWasmPath: "/work/browser-random-machine.ini",
|
|
parameterWasmPath: "/work/browser-random-linuxcnc.var",
|
|
toolTableWasmPath: "/work/browser-random-tool.tbl",
|
|
},
|
|
);
|
|
if (
|
|
loadedRandomSession.parameters.opfsPath !==
|
|
"linuxcnc/machines/browser-random-toolchanger/linuxcnc.var"
|
|
) {
|
|
throw new Error(`unexpected default parameter path: ${loadedRandomSession.parameters.opfsPath}`);
|
|
}
|
|
if (
|
|
loadedRandomSession.toolTable.opfsPath !==
|
|
"linuxcnc/machines/browser-random-toolchanger/tool.tbl"
|
|
) {
|
|
throw new Error(`unexpected default tool path: ${loadedRandomSession.toolTable.opfsPath}`);
|
|
}
|
|
verifyExpectedOutput(
|
|
"opfs_load_random_tool_table",
|
|
loadedRandomSession.toolTable.result,
|
|
[
|
|
"tooldata_random_toolchanger=1",
|
|
"tooldata_load=0",
|
|
"tool_pocket_7.toolno=2",
|
|
"tool_pocket_7.pocketno=7",
|
|
"tool_pocket_7.z=3.125",
|
|
"tool_pocket_7.diameter=1.5",
|
|
"tool_pocket_7.frontangle=12",
|
|
"tool_pocket_7.backangle=34",
|
|
"tool_pocket_7.orientation=4",
|
|
"tool_pocket_7.comment=browser random finish tool",
|
|
"tool_pocket_9.toolno=5",
|
|
"tool_pocket_9.pocketno=9",
|
|
"tool_index_for_tool_2=7",
|
|
].join("\n"),
|
|
);
|
|
const savedRandomToolTable = await saveMachineToolTableToOpfs(
|
|
interp,
|
|
"browser-random-toolchanger",
|
|
{ wasmPath: "/work/browser-random-tool.tbl" },
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_save_random_tool_table",
|
|
savedRandomToolTable.result,
|
|
[
|
|
"tooldata_save=0",
|
|
"tool_pocket_7.toolno=2",
|
|
"tool_pocket_9.toolno=5",
|
|
].join("\n"),
|
|
);
|
|
const savedRandomMachineFiles = await loadMachineTextFiles(
|
|
"browser-random-toolchanger",
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_saved_random_tool_table_text",
|
|
savedRandomMachineFiles.toolTable,
|
|
[
|
|
"T2",
|
|
"P7",
|
|
";browser random finish tool",
|
|
"T5",
|
|
"P9",
|
|
";browser random rough tool",
|
|
].join("\n"),
|
|
);
|
|
|
|
await saveMachineTextFiles("browser-ini-file-session", {
|
|
ini: [
|
|
"[EMC]",
|
|
"MACHINE = browser-ini-file-session",
|
|
"",
|
|
"[RS274NGC]",
|
|
"PARAMETER_FILE = browser-custom.var",
|
|
"",
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = browser-custom-tool.tbl",
|
|
"",
|
|
].join("\n"),
|
|
});
|
|
await saveTextFile(
|
|
"linuxcnc/machines/browser-ini-file-session/browser-custom.var",
|
|
[
|
|
"5161 31.25",
|
|
"5162 62.5",
|
|
"5220 1",
|
|
"5221 4.5",
|
|
"5399 99",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
await saveTextFile(
|
|
"linuxcnc/machines/browser-ini-file-session/browser-custom-tool.tbl",
|
|
"T8 P8 Z4.5 D0.5 ;browser custom tool\n",
|
|
);
|
|
const loadedIniFileSession = await loadMachineSessionFromOpfs(
|
|
interp,
|
|
"browser-ini-file-session",
|
|
{
|
|
iniSdk: ini,
|
|
iniWasmPath: "/work/browser-ini-file-session.ini",
|
|
parameterWasmPath: "/work/browser-custom.var",
|
|
toolTableWasmPath: "/work/browser-custom-tool.tbl",
|
|
},
|
|
);
|
|
if (
|
|
loadedIniFileSession.parameters.opfsPath !==
|
|
"linuxcnc/machines/browser-ini-file-session/browser-custom.var"
|
|
) {
|
|
throw new Error(`unexpected custom parameter path: ${loadedIniFileSession.parameters.opfsPath}`);
|
|
}
|
|
if (
|
|
loadedIniFileSession.toolTable.opfsPath !==
|
|
"linuxcnc/machines/browser-ini-file-session/browser-custom-tool.tbl"
|
|
) {
|
|
throw new Error(`unexpected custom tool path: ${loadedIniFileSession.toolTable.opfsPath}`);
|
|
}
|
|
verifyExpectedOutput(
|
|
"opfs_load_ini_named_parameter_file",
|
|
loadedIniFileSession.parameters.result,
|
|
[
|
|
"restore_parameters=0",
|
|
"parameter_5161=31.25",
|
|
"parameter_5162=62.5",
|
|
"parameter_5221=4.5",
|
|
"parameter_5399=99",
|
|
].join("\n"),
|
|
);
|
|
const savedIniNamedParameters = await saveMachineParametersToOpfs(
|
|
interp,
|
|
"browser-ini-file-session",
|
|
{
|
|
5161: 33.75,
|
|
5162: 67.5,
|
|
5220: 1.0,
|
|
5221: 5.25,
|
|
5399: 199,
|
|
},
|
|
{
|
|
opfsPath: loadedIniFileSession.parameters.opfsPath,
|
|
wasmPath: loadedIniFileSession.parameters.wasmPath,
|
|
},
|
|
);
|
|
if (
|
|
savedIniNamedParameters.opfsPath !==
|
|
"linuxcnc/machines/browser-ini-file-session/browser-custom.var"
|
|
) {
|
|
throw new Error(`unexpected custom saved parameter path: ${savedIniNamedParameters.opfsPath}`);
|
|
}
|
|
if (
|
|
savedIniNamedParameters.backupOpfsPath !==
|
|
"linuxcnc/machines/browser-ini-file-session/browser-custom.var.bak"
|
|
) {
|
|
throw new Error(`unexpected custom parameter backup path: ${savedIniNamedParameters.backupOpfsPath}`);
|
|
}
|
|
verifyExpectedOutput(
|
|
"opfs_save_ini_named_parameter_file",
|
|
savedIniNamedParameters.result,
|
|
[
|
|
"save_parameters=0",
|
|
"parameter_5161=33.75",
|
|
"parameter_5162=67.5",
|
|
"parameter_5221=5.25",
|
|
"parameter_5399=199",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_saved_ini_named_parameter_text",
|
|
await loadTextFile(loadedIniFileSession.parameters.opfsPath),
|
|
[
|
|
"5161\t33.750000",
|
|
"5162\t67.500000",
|
|
"5221\t5.250000",
|
|
"5399\t199.000000",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_saved_ini_named_parameter_backup_text",
|
|
await loadTextFile(savedIniNamedParameters.backupOpfsPath),
|
|
[
|
|
"5161 31.25",
|
|
"5162 62.5",
|
|
"5221 4.5",
|
|
"5399 99",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_load_ini_named_tool_table",
|
|
loadedIniFileSession.toolTable.result,
|
|
[
|
|
"tooldata_load=0",
|
|
"tool_1.toolno=8",
|
|
"tool_1.pocketno=8",
|
|
"tool_1.z=4.5",
|
|
"tool_1.diameter=0.5",
|
|
"tool_1.comment=browser custom tool",
|
|
].join("\n"),
|
|
);
|
|
const savedIniNamedToolTable = await saveMachineToolTableToOpfs(
|
|
interp,
|
|
"browser-ini-file-session",
|
|
{
|
|
opfsPath: loadedIniFileSession.toolTable.opfsPath,
|
|
wasmPath: loadedIniFileSession.toolTable.wasmPath,
|
|
},
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_save_ini_named_tool_table",
|
|
savedIniNamedToolTable.result,
|
|
[
|
|
"tooldata_save=0",
|
|
"tool_1.toolno=8",
|
|
"tool_1.pocketno=8",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_saved_ini_named_tool_table_text",
|
|
await loadTextFile(loadedIniFileSession.toolTable.opfsPath),
|
|
[
|
|
"T8",
|
|
"P8",
|
|
"Z+4.500000",
|
|
"D+0.500000",
|
|
";browser custom tool",
|
|
].join("\n"),
|
|
);
|
|
|
|
await saveMachineTextFiles("browser-ini-override-session", {
|
|
ini: [
|
|
"[EMC]",
|
|
"MACHINE = browser-ini-override-session",
|
|
"",
|
|
"[RS274NGC]",
|
|
"PARAMETER_FILE = browser-ignored.var",
|
|
"",
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = browser-ignored-tool.tbl",
|
|
"",
|
|
].join("\n"),
|
|
});
|
|
await saveTextFile(
|
|
"linuxcnc/machines/browser-ini-override-session/browser-explicit.var",
|
|
[
|
|
"5161 41.25",
|
|
"5162 82.5",
|
|
"5220 1",
|
|
"5221 6.5",
|
|
"5399 101",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
await saveTextFile(
|
|
"linuxcnc/machines/browser-ini-override-session/browser-explicit-tool.tbl",
|
|
"T9 P9 Z5.5 D0.625 ;browser explicit tool\n",
|
|
);
|
|
const loadedIniOverrideSession = await loadMachineSessionFromOpfs(
|
|
interp,
|
|
"browser-ini-override-session",
|
|
{
|
|
iniSdk: ini,
|
|
iniWasmPath: "/work/browser-ini-override-session.ini",
|
|
parameterFilename: "browser-explicit.var",
|
|
parameterWasmPath: "/work/browser-explicit.var",
|
|
toolTableFilename: "browser-explicit-tool.tbl",
|
|
toolTableWasmPath: "/work/browser-explicit-tool.tbl",
|
|
},
|
|
);
|
|
if (
|
|
loadedIniOverrideSession.parameters.opfsPath !==
|
|
"linuxcnc/machines/browser-ini-override-session/browser-explicit.var"
|
|
) {
|
|
throw new Error(`unexpected explicit parameter path: ${loadedIniOverrideSession.parameters.opfsPath}`);
|
|
}
|
|
if (
|
|
loadedIniOverrideSession.toolTable.opfsPath !==
|
|
"linuxcnc/machines/browser-ini-override-session/browser-explicit-tool.tbl"
|
|
) {
|
|
throw new Error(`unexpected explicit tool path: ${loadedIniOverrideSession.toolTable.opfsPath}`);
|
|
}
|
|
verifyExpectedOutput(
|
|
"opfs_load_explicit_parameter_file",
|
|
loadedIniOverrideSession.parameters.result,
|
|
[
|
|
"restore_parameters=0",
|
|
"parameter_5161=41.25",
|
|
"parameter_5162=82.5",
|
|
"parameter_5221=6.5",
|
|
"parameter_5399=101",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_load_explicit_tool_table",
|
|
loadedIniOverrideSession.toolTable.result,
|
|
[
|
|
"tooldata_load=0",
|
|
"tool_1.toolno=9",
|
|
"tool_1.pocketno=9",
|
|
"tool_1.z=5.5",
|
|
"tool_1.diameter=0.625",
|
|
"tool_1.comment=browser explicit tool",
|
|
].join("\n"),
|
|
);
|
|
const savedExplicitParameters = await saveMachineParametersToOpfs(
|
|
interp,
|
|
"browser-ini-override-session",
|
|
{
|
|
5161: 51.25,
|
|
5162: 102.5,
|
|
5220: 1.0,
|
|
5221: 7.5,
|
|
5399: 202,
|
|
},
|
|
{
|
|
opfsPath: loadedIniOverrideSession.parameters.opfsPath,
|
|
wasmPath: loadedIniOverrideSession.parameters.wasmPath,
|
|
},
|
|
);
|
|
if (
|
|
savedExplicitParameters.opfsPath !==
|
|
"linuxcnc/machines/browser-ini-override-session/browser-explicit.var"
|
|
) {
|
|
throw new Error(`unexpected explicit saved parameter path: ${savedExplicitParameters.opfsPath}`);
|
|
}
|
|
if (
|
|
savedExplicitParameters.backupOpfsPath !==
|
|
"linuxcnc/machines/browser-ini-override-session/browser-explicit.var.bak"
|
|
) {
|
|
throw new Error(`unexpected explicit backup path: ${savedExplicitParameters.backupOpfsPath}`);
|
|
}
|
|
verifyExpectedOutput(
|
|
"opfs_save_explicit_parameter_file",
|
|
savedExplicitParameters.result,
|
|
[
|
|
"save_parameters=0",
|
|
"parameter_5161=51.25",
|
|
"parameter_5162=102.5",
|
|
"parameter_5221=7.5",
|
|
"parameter_5399=202",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_saved_explicit_parameter_text",
|
|
await loadTextFile(loadedIniOverrideSession.parameters.opfsPath),
|
|
[
|
|
"5161\t51.250000",
|
|
"5162\t102.500000",
|
|
"5221\t7.500000",
|
|
"5399\t202.000000",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_saved_explicit_parameter_backup_text",
|
|
await loadTextFile(savedExplicitParameters.backupOpfsPath),
|
|
[
|
|
"5161 41.25",
|
|
"5162 82.5",
|
|
"5221 6.5",
|
|
"5399 101",
|
|
].join("\n"),
|
|
);
|
|
const savedExplicitToolTable = await saveMachineToolTableToOpfs(
|
|
interp,
|
|
"browser-ini-override-session",
|
|
{
|
|
opfsPath: loadedIniOverrideSession.toolTable.opfsPath,
|
|
wasmPath: loadedIniOverrideSession.toolTable.wasmPath,
|
|
},
|
|
);
|
|
if (
|
|
savedExplicitToolTable.opfsPath !==
|
|
"linuxcnc/machines/browser-ini-override-session/browser-explicit-tool.tbl"
|
|
) {
|
|
throw new Error(`unexpected explicit saved tool path: ${savedExplicitToolTable.opfsPath}`);
|
|
}
|
|
verifyExpectedOutput(
|
|
"opfs_save_explicit_tool_table",
|
|
savedExplicitToolTable.result,
|
|
[
|
|
"tooldata_save=0",
|
|
"tool_1.toolno=9",
|
|
"tool_1.pocketno=9",
|
|
].join("\n"),
|
|
);
|
|
verifyExpectedOutput(
|
|
"opfs_saved_explicit_tool_table_text",
|
|
await loadTextFile(loadedIniOverrideSession.toolTable.opfsPath),
|
|
[
|
|
"T9",
|
|
"P9",
|
|
"Z+5.500000",
|
|
"D+0.625000",
|
|
";browser explicit tool",
|
|
].join("\n"),
|
|
);
|
|
|
|
await saveMachineTextFiles("browser-invalid-param-file-session", {
|
|
ini: [
|
|
"[EMC]",
|
|
"MACHINE = browser-invalid-param-file-session",
|
|
"",
|
|
"[RS274NGC]",
|
|
"PARAMETER_FILE = ../escape.var",
|
|
"",
|
|
].join("\n"),
|
|
});
|
|
await verifyRejects(
|
|
"opfs_reject_invalid_ini_parameter_file",
|
|
() => loadMachineSessionFromOpfs(
|
|
interp,
|
|
"browser-invalid-param-file-session",
|
|
{
|
|
iniSdk: ini,
|
|
iniWasmPath: "/work/browser-invalid-param-file-session.ini",
|
|
},
|
|
),
|
|
/Invalid parameter filename/,
|
|
);
|
|
|
|
await saveMachineTextFiles("browser-invalid-tool-file-session", {
|
|
ini: [
|
|
"[EMC]",
|
|
"MACHINE = browser-invalid-tool-file-session",
|
|
"",
|
|
"[EMCIO]",
|
|
"TOOL_TABLE = nested/tool.tbl",
|
|
"",
|
|
].join("\n"),
|
|
});
|
|
await verifyRejects(
|
|
"opfs_reject_invalid_ini_tool_table",
|
|
() => loadMachineSessionFromOpfs(
|
|
interp,
|
|
"browser-invalid-tool-file-session",
|
|
{
|
|
iniSdk: ini,
|
|
iniWasmPath: "/work/browser-invalid-tool-file-session.ini",
|
|
},
|
|
),
|
|
/Invalid tool table filename/,
|
|
);
|
|
|
|
status.textContent = "browser_interp_smoke=ok";
|
|
} catch (error) {
|
|
status.textContent = `browser_interp_smoke=fail ${error.stack || error.message}`;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|