继续完成 web-rtcp-5axis-sim-plan
结论:完成 LinuxCNC kinematics WASM ABI 覆盖,并将 web-rtcp-5axis-sim-plan 的 RTCP frame/boundary adapter 接到 xyzac-trt kinematics SDK;Node、build、browser smoke 验证通过。
This commit is contained in:
230
wasm-port/tests/wasm/node/verify_kinematics_wasm.mjs
Normal file
230
wasm-port/tests/wasm/node/verify_kinematics_wasm.mjs
Normal file
@@ -0,0 +1,230 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
createLinuxCncKinematicsSdk,
|
||||
linuxCncKinematicsWasmFile,
|
||||
supportedLinuxCncKinematicsModules,
|
||||
} from "../../../runtime/sdk/src/index.js";
|
||||
|
||||
const EXPECTED_MODULES = [
|
||||
"trivkins",
|
||||
"5axiskins",
|
||||
"xyzac-trt",
|
||||
"xyzbc-trt",
|
||||
"corexy",
|
||||
"rotate",
|
||||
"rose",
|
||||
"max",
|
||||
"lineardelta",
|
||||
"rotarydelta",
|
||||
"scorbot",
|
||||
"tripod",
|
||||
"scara",
|
||||
"puma",
|
||||
"genser",
|
||||
"genhex",
|
||||
"pentakins",
|
||||
];
|
||||
|
||||
const EXPECTED_META = {
|
||||
trivkins: { type: 1, switchable: 0 },
|
||||
"5axiskins": { type: 4, switchable: 1 },
|
||||
"xyzac-trt": { type: 4, switchable: 1 },
|
||||
"xyzbc-trt": { type: 4, switchable: 1 },
|
||||
corexy: { type: 4, switchable: 0 },
|
||||
rotate: { type: 4, switchable: 0 },
|
||||
rose: { type: 4, switchable: 0 },
|
||||
max: { type: 4, switchable: 0 },
|
||||
lineardelta: { type: 4, switchable: 0 },
|
||||
rotarydelta: { type: 4, switchable: 0 },
|
||||
scorbot: { type: 4, switchable: 0 },
|
||||
tripod: { type: 4, switchable: 0 },
|
||||
scara: { type: 4, switchable: 1 },
|
||||
puma: { type: 4, switchable: 1 },
|
||||
genser: { type: 4, switchable: 1 },
|
||||
genhex: { type: 4, switchable: 1 },
|
||||
pentakins: { type: 4, switchable: 0 },
|
||||
};
|
||||
|
||||
function near(actual, expected, tolerance = 1e-7) {
|
||||
return Math.abs(actual - expected) < tolerance;
|
||||
}
|
||||
|
||||
function assertNear(actual, expected, label, tolerance = 1e-7) {
|
||||
assert.equal(near(actual, expected, tolerance), true, `${label}: ${actual} != ${expected}`);
|
||||
}
|
||||
|
||||
function assertArrayNear(actual, expected, label, tolerance = 1e-7) {
|
||||
assert.equal(actual.length >= expected.length, true, `${label}: missing values`);
|
||||
expected.forEach((value, index) => assertNear(actual[index], value, `${label}[${index}]`, tolerance));
|
||||
}
|
||||
|
||||
function assertPoseNear(actual, expected, label, tolerance = 1e-7) {
|
||||
for (const key of ["x", "y", "z", "a", "b", "c", "u", "v", "w"]) {
|
||||
assertNear(actual[key] ?? 0, expected[key] ?? 0, `${label}.${key}`, tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
function assertProbeBasics(moduleId, probe) {
|
||||
assert.equal(probe.includes("kinematics_init=0"), true, `${moduleId}: init probe`);
|
||||
assert.equal(
|
||||
probe.includes(`kinematics_type=${EXPECTED_META[moduleId].type}`),
|
||||
true,
|
||||
`${moduleId}: type probe`,
|
||||
);
|
||||
assert.equal(
|
||||
probe.includes(`kinematics_switchable=${EXPECTED_META[moduleId].switchable}`),
|
||||
true,
|
||||
`${moduleId}: switchable probe`,
|
||||
);
|
||||
}
|
||||
|
||||
function assertSwitchIdentity(kins, moduleId, joints, expectedPose) {
|
||||
assert.equal(kins.switchKinematics(1), 0, `${moduleId}: switch identity`);
|
||||
const identity = kins.forward(joints);
|
||||
assert.equal(identity.rc, 0, `${moduleId}: identity forward`);
|
||||
assertPoseNear(identity.pose, expectedPose, `${moduleId}: identity pose`);
|
||||
}
|
||||
|
||||
function assertJointRoundtrip(kins, moduleId, joints, jointCount = joints.length, tolerance = 1e-7) {
|
||||
if (kins.switchable()) {
|
||||
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
|
||||
}
|
||||
const forward = kins.forward(joints);
|
||||
assert.equal(forward.rc, 0, `${moduleId}: forward`);
|
||||
const inverse = kins.inverse(forward.pose, jointCount, { seedJoints: joints });
|
||||
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
|
||||
assertArrayNear(inverse.joints, joints.slice(0, jointCount), `${moduleId}: joints`, tolerance);
|
||||
}
|
||||
|
||||
function assertPoseRoundtrip(kins, moduleId, pose, jointCount, options = {}) {
|
||||
if (kins.switchable()) {
|
||||
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
|
||||
}
|
||||
const inverse = kins.inverse(pose, jointCount, options.inverseOptions ?? {});
|
||||
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
|
||||
if (options.warmupForward) {
|
||||
kins.forward(inverse.joints, { seedPose: pose });
|
||||
}
|
||||
const forward = kins.forward(inverse.joints, { seedPose: pose });
|
||||
assert.equal(forward.rc, 0, `${moduleId}: forward`);
|
||||
assertPoseNear(forward.pose, pose, `${moduleId}: pose`, options.tolerance ?? 1e-7);
|
||||
return { inverse, forward };
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const rootDir = resolve(__dirname, "../../..");
|
||||
|
||||
assert.deepEqual(supportedLinuxCncKinematicsModules(), EXPECTED_MODULES);
|
||||
|
||||
for (const moduleId of supportedLinuxCncKinematicsModules()) {
|
||||
const wasmFile = linuxCncKinematicsWasmFile(moduleId);
|
||||
const wasmPath = resolve(rootDir, "build/wasm/kinematics", wasmFile);
|
||||
const kins = await createLinuxCncKinematicsSdk({
|
||||
moduleId,
|
||||
moduleOptions: {
|
||||
wasmBinary: readFileSync(wasmPath),
|
||||
print() {},
|
||||
printErr(message) {
|
||||
console.error(`[${moduleId}] ${message}`);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(kins.apiName, "linuxcnc-kinematics-wasm-sdk");
|
||||
assert.equal(kins.wasmFile, wasmFile);
|
||||
for (const functionName of ["lckins_forward", "lckins_inverse", "lckins_run_probe"]) {
|
||||
assert.equal(kins.hasWasmFunction(functionName), true, `${moduleId}: ${functionName}`);
|
||||
}
|
||||
assert.equal(kins.type(), EXPECTED_META[moduleId].type, `${moduleId}: type`);
|
||||
assert.equal(kins.switchable(), EXPECTED_META[moduleId].switchable, `${moduleId}: switchable`);
|
||||
assertProbeBasics(moduleId, kins.runProbe());
|
||||
|
||||
switch (moduleId) {
|
||||
case "trivkins":
|
||||
assertJointRoundtrip(kins, moduleId, [1, 2, 3, 4, 5, 6, 7, 8, 9], 9);
|
||||
break;
|
||||
case "5axiskins":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 45, 30, 5], 6);
|
||||
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 45, 30, 5], { x: 10, y: 20, z: 30, b: 45, c: 30, w: 5 });
|
||||
break;
|
||||
case "xyzac-trt":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 25, 40], 5);
|
||||
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 25, 40], { x: 10, y: 20, z: 30, a: 25, c: 40 });
|
||||
break;
|
||||
case "xyzbc-trt":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 35, 40], 5);
|
||||
assertSwitchIdentity(kins, moduleId, [10, 20, 30, 35, 40], { x: 10, y: 20, z: 30, b: 35, c: 40 });
|
||||
break;
|
||||
case "corexy":
|
||||
assertJointRoundtrip(kins, moduleId, [12, 4, 3, 4, 5, 6, 7, 8, 9], 9);
|
||||
break;
|
||||
case "rotate":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 4, 5, 30, 7, 8, 9], 9);
|
||||
break;
|
||||
case "rose":
|
||||
assertJointRoundtrip(kins, moduleId, [12, 5, 30], 3);
|
||||
break;
|
||||
case "max":
|
||||
assertJointRoundtrip(kins, moduleId, [10, 20, 30, 4, 0, 25, 0, 0, 3.5], 9);
|
||||
break;
|
||||
case "lineardelta":
|
||||
assertPoseRoundtrip(kins, moduleId, { x: 10, y: 20, z: -30, a: 1, b: 2, c: 3, u: 4, v: 5, w: 6 }, 9);
|
||||
break;
|
||||
case "rotarydelta":
|
||||
assertPoseRoundtrip(kins, moduleId, { x: 0, y: 0, z: -12, a: 1, b: 2, c: 3, u: 4, v: 5, w: 6 }, 9);
|
||||
break;
|
||||
case "scorbot": {
|
||||
const forward = kins.forward([25, 20, 10, 4, 5]);
|
||||
assert.equal(forward.rc, 0, `${moduleId}: forward`);
|
||||
const inverse = kins.inverse(forward.pose, 5);
|
||||
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
|
||||
const roundtrip = kins.forward(inverse.joints);
|
||||
assert.equal(roundtrip.rc, 0, `${moduleId}: roundtrip forward`);
|
||||
assertPoseNear(roundtrip.pose, forward.pose, `${moduleId}: pose`);
|
||||
break;
|
||||
}
|
||||
case "tripod":
|
||||
assertPoseRoundtrip(kins, moduleId, { x: 0.25, y: 0.25, z: 0.5 }, 3);
|
||||
break;
|
||||
case "scara":
|
||||
assertJointRoundtrip(kins, moduleId, [20, 110, 30, 15, 4, 5], 6);
|
||||
assertSwitchIdentity(kins, moduleId, [20, 110, 30, 15, 4, 5], { x: 20, y: 110, z: 30, a: 15, b: 4, c: 5 });
|
||||
break;
|
||||
case "puma": {
|
||||
if (kins.switchable()) {
|
||||
assert.equal(kins.switchKinematics(0), 0, `${moduleId}: switch primary`);
|
||||
}
|
||||
const forward = kins.forward([20, -30, 40, 15, 35, -25]);
|
||||
assert.equal(forward.rc, 0, `${moduleId}: forward`);
|
||||
const inverse = kins.inverse(forward.pose, 6);
|
||||
assert.equal(inverse.rc, 0, `${moduleId}: inverse`);
|
||||
const roundtrip = kins.forward(inverse.joints);
|
||||
assert.equal(roundtrip.rc, 0, `${moduleId}: roundtrip forward`);
|
||||
assertPoseNear(roundtrip.pose, forward.pose, `${moduleId}: pose`);
|
||||
assertSwitchIdentity(kins, moduleId, [20, -30, 40, 15, 35, -25], { x: 20, y: -30, z: 40, a: 15, b: 35, c: -25 });
|
||||
break;
|
||||
}
|
||||
case "genser":
|
||||
assertJointRoundtrip(kins, moduleId, [10, -15, 20, 5, -10, 12, 7, 8, 9], 9);
|
||||
assertSwitchIdentity(kins, moduleId, [10, -15, 20, 5, -10, 12, 7, 8, 9], { x: 10, y: -15, z: 20, a: 5, b: -10, c: 12, u: 7, v: 8, w: 9 });
|
||||
break;
|
||||
case "genhex":
|
||||
assertPoseRoundtrip(kins, moduleId, { x: 1.25, y: -2.5, z: 3.75, a: 1, b: -2, c: 3 }, 6, { warmupForward: true });
|
||||
assert.equal(kins.switchKinematics(1), 0, `${moduleId}: switch alternate`);
|
||||
break;
|
||||
case "pentakins": {
|
||||
const { inverse } = assertPoseRoundtrip(kins, moduleId, { x: 12.5, y: -8.25, z: 25, a: 2, b: -1.5 }, 5, { tolerance: 1e-6 });
|
||||
assert.equal(inverse.joints.every((value) => value > 0), true, `${moduleId}: positive struts`);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`missing kinematics verification for ${moduleId}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("kinematics_wasm_node_smoke=ok");
|
||||
9
wasm-port/tests/wasm/node/verify_kinematics_wasm.sh
Executable file
9
wasm-port/tests/wasm/node/verify_kinematics_wasm.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
|
||||
if [[ "${SKIP_KINEMATICS_BUILD:-0}" != "1" ]]; then
|
||||
"$ROOT_DIR/tools/build_kinematics_wasm.sh"
|
||||
fi
|
||||
node "$ROOT_DIR/tests/wasm/node/verify_kinematics_wasm.mjs"
|
||||
@@ -108,6 +108,7 @@ const nativeRuntimeProbePassEvidenceContractPath = resolve(buildDir, "native-run
|
||||
const promotionCandidatesPath = resolve(buildDir, "promotion-candidates.tsv");
|
||||
const evidenceExpansionCandidatesPath = resolve(buildDir, "evidence-expansion-candidates.tsv");
|
||||
const remainingSkipMainProgramPromotionAuditPath = resolve(buildDir, "remaining-skip-main-program-promotion-audit.tsv");
|
||||
const remainingSkipSimulationImplementationCoveragePath = resolve(buildDir, "remaining-skip-simulation-implementation-coverage.tsv");
|
||||
const blockedRuntimePromotionLockPath = resolve(buildDir, "blocked-runtime-promotion-lock.tsv");
|
||||
const runtimeBoundaryPromotionReadinessPath = resolve(buildDir, "runtime-boundary-promotion-readiness.tsv");
|
||||
const runtimeBoundaryPromotionBlockersPath = resolve(buildDir, "runtime-boundary-promotion-blockers.tsv");
|
||||
@@ -168,6 +169,7 @@ const generatedSimConfigInventoryArtifactPaths = [
|
||||
nativeRuntimeProbeExecutionPlanPath,
|
||||
nativeRuntimeProbePassEvidenceContractPath,
|
||||
promotionCandidatesPath,
|
||||
remainingSkipSimulationImplementationCoveragePath,
|
||||
remainingSkipMainProgramPromotionAuditPath,
|
||||
blockedRuntimePromotionLockPath,
|
||||
runtimeBoundaryPromotionReadinessPath,
|
||||
@@ -6925,7 +6927,7 @@ function verifyBoundaryPhaseCompletionSummaryRows(rows) {
|
||||
const rowByCriterion = new Map(parsedRows.map((row) => [row.criterion, row]));
|
||||
assert.equal(
|
||||
rowByCriterion.get("wasm_inventory_artifact_documentation_coverage")?.count,
|
||||
"60",
|
||||
"61",
|
||||
"WASM artifact documentation completion count must match generated artifact baseline",
|
||||
);
|
||||
assert.equal(
|
||||
@@ -9678,7 +9680,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
|
||||
);
|
||||
assert.equal(
|
||||
artifactNames.length,
|
||||
60,
|
||||
61,
|
||||
"generated sim-config inventory artifact list count drift",
|
||||
);
|
||||
assert.deepEqual(
|
||||
@@ -9712,6 +9714,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
|
||||
"python-remap-runtime-gates.tsv",
|
||||
"python-remap-wasm-node-row-proof.tsv",
|
||||
"remaining-skip-main-program-promotion-audit.tsv",
|
||||
"remaining-skip-simulation-implementation-coverage.tsv",
|
||||
"runtime-boundary-contract-summary.tsv",
|
||||
"runtime-boundary-family-host-readiness.tsv",
|
||||
"runtime-boundary-host-preflight.tsv",
|
||||
@@ -9778,7 +9781,7 @@ function verifyGeneratedArtifactDocumentationCoverage({
|
||||
"generated sim-config inventory artifacts must be listed in browser smoke",
|
||||
);
|
||||
assert.ok(
|
||||
browserSmokeText.includes("wasmArtifactNames.length !== 60"),
|
||||
browserSmokeText.includes("wasmArtifactNames.length !== 61"),
|
||||
"browser smoke must keep generated WASM artifact fixed-count guard",
|
||||
);
|
||||
assert.ok(
|
||||
@@ -13673,6 +13676,223 @@ function verifyRemainingSkipMainProgramPromotionAuditRows({
|
||||
return parsedRows;
|
||||
}
|
||||
|
||||
function simulationImplementationMode(row) {
|
||||
if (row.reason === "ASSET-ONLY") {
|
||||
return "source_asset_dependency";
|
||||
}
|
||||
if (row.reason === "NON_MAIN_CLASS") {
|
||||
return "macro_load_or_non_main_class_coverage";
|
||||
}
|
||||
if (row.reason === "L4-USER-M-PROCESS") {
|
||||
return "linuxcnc_runtime_boundary_virtual_hal_state_proof";
|
||||
}
|
||||
if (row.reason === "UPSTREAM-DEMO") {
|
||||
return "upstream_demo_preserved_invalid_motion_source";
|
||||
}
|
||||
return "unclassified_skip";
|
||||
}
|
||||
|
||||
function simulationImplementationStatus(row) {
|
||||
if (row.reason === "ASSET-ONLY") {
|
||||
return "implemented_as_linuxcnc_source_asset_dependency_not_standalone_main";
|
||||
}
|
||||
if (row.reason === "NON_MAIN_CLASS") {
|
||||
return "implemented_as_linuxcnc_macro_load_or_non_main_class_not_standalone_main";
|
||||
}
|
||||
if (row.reason === "L4-USER-M-PROCESS") {
|
||||
return "implemented_as_source_derived_boundary_state_proof_runtime_execution_blocked";
|
||||
}
|
||||
if (row.reason === "UPSTREAM-DEMO") {
|
||||
return "implemented_as_preserved_upstream_demo_edge_invalid_motion_not_forced_pass";
|
||||
}
|
||||
return "skip_kind_requires_review";
|
||||
}
|
||||
|
||||
function simulationImplementationNextStep(row) {
|
||||
if (row.reason === "L4-USER-M-PROCESS") {
|
||||
return "run_opt_in_native_runtime_probe_before_any_inventory_promotion";
|
||||
}
|
||||
if (row.reason === "UPSTREAM-DEMO") {
|
||||
return "wait_for_upstream_source_fix_then_regenerate_inventory";
|
||||
}
|
||||
return "use_as_dependency_or_class_coverage_no_inventory_promotion";
|
||||
}
|
||||
|
||||
function remainingSkipSimulationImplementationCoverageRows({
|
||||
summaryRows,
|
||||
boundaryRows,
|
||||
remainingSkipAuditRows,
|
||||
}) {
|
||||
const boundaryByPath = new Map(boundaryRows.map((row) => [row.path, row]));
|
||||
const auditByPath = new Map(remainingSkipAuditRows.map((row) => [row.path, row]));
|
||||
|
||||
return summaryRows
|
||||
.map((row) => {
|
||||
const [
|
||||
path,
|
||||
inventoryStatus,
|
||||
reason,
|
||||
className,
|
||||
nativeStatus,
|
||||
nativeExpectedFailure,
|
||||
] = row.split("\t");
|
||||
return {
|
||||
path,
|
||||
inventoryStatus,
|
||||
reason,
|
||||
className,
|
||||
nativeStatus,
|
||||
nativeExpectedFailure,
|
||||
};
|
||||
})
|
||||
.filter((row) => row.inventoryStatus === "SKIP")
|
||||
.sort((left, right) => left.path.localeCompare(right.path))
|
||||
.map((row) => {
|
||||
const boundary = boundaryByPath.get(row.path);
|
||||
const audit = auditByPath.get(row.path);
|
||||
const sourceRel = `configs/sim/${row.path}`;
|
||||
const sourceAvailable = sourceConfigPathExists(row.path);
|
||||
const implementationMode = simulationImplementationMode(row);
|
||||
const standaloneMainReady = row.className === "main" &&
|
||||
row.reason === "-" &&
|
||||
row.nativeStatus === "PASS";
|
||||
const runtimePromotionBlocked = ["L4-USER-M-PROCESS", "UPSTREAM-DEMO"].includes(row.reason);
|
||||
return [
|
||||
row.path,
|
||||
boundary?.ini ?? audit?.ini ?? "-",
|
||||
row.reason,
|
||||
row.className,
|
||||
row.nativeStatus,
|
||||
row.nativeExpectedFailure,
|
||||
sourceRel,
|
||||
flagValue(sourceAvailable),
|
||||
implementationMode,
|
||||
simulationImplementationStatus(row),
|
||||
flagValue(row.className === "main"),
|
||||
flagValue(standaloneMainReady),
|
||||
boundary?.dependencies ?? audit?.dependency_class ?? "-",
|
||||
boundary?.recommended_blocked ?? audit?.blocked_kind ?? row.reason,
|
||||
audit?.promotion_ready ?? "0",
|
||||
audit?.promotion_allowed ?? "0",
|
||||
flagValue(runtimePromotionBlocked),
|
||||
"1",
|
||||
simulationImplementationNextStep(row),
|
||||
].map(tsvValue).join("\t");
|
||||
});
|
||||
}
|
||||
|
||||
function verifyRemainingSkipSimulationImplementationCoverageRows({
|
||||
rows,
|
||||
summaryRows,
|
||||
remainingSkipAuditRows,
|
||||
}) {
|
||||
const expectedHeaders = [
|
||||
"path",
|
||||
"ini",
|
||||
"skip_kind",
|
||||
"class",
|
||||
"native_status",
|
||||
"native_expected_failure",
|
||||
"linuxcnc_source_path",
|
||||
"linuxcnc_source_available",
|
||||
"simulation_implementation_mode",
|
||||
"simulation_implementation_status",
|
||||
"main_program_class",
|
||||
"standalone_main_program_ready",
|
||||
"dependency_class",
|
||||
"blocked_kind",
|
||||
"promotion_ready",
|
||||
"promotion_allowed",
|
||||
"runtime_promotion_blocked",
|
||||
"simulation_coverage_ready",
|
||||
"recommended_next_step",
|
||||
];
|
||||
const parsedRows = parseTsv(
|
||||
`${expectedHeaders.join("\t")}\n${rows.join("\n")}\n`,
|
||||
expectedHeaders,
|
||||
);
|
||||
const skippedSummaryRows = summaryRows
|
||||
.map((row) => {
|
||||
const [path, inventoryStatus, reason, className] = row.split("\t");
|
||||
return { path, inventoryStatus, reason, className };
|
||||
})
|
||||
.filter((row) => row.inventoryStatus === "SKIP");
|
||||
const auditByPath = new Map(remainingSkipAuditRows.map((row) => [row.path, row]));
|
||||
|
||||
assert.equal(parsedRows.length, 77, "remaining skip simulation implementation coverage count drift");
|
||||
assert.deepEqual(
|
||||
parsedRows.map((row) => row.path).sort(),
|
||||
skippedSummaryRows.map((row) => row.path).sort(),
|
||||
"remaining skip simulation implementation coverage must cover every skipped row",
|
||||
);
|
||||
assert.deepEqual(
|
||||
Object.fromEntries(
|
||||
[...parsedRows.reduce((counts, row) => {
|
||||
counts.set(row.skip_kind, (counts.get(row.skip_kind) ?? 0) + 1);
|
||||
return counts;
|
||||
}, new Map()).entries()].sort(),
|
||||
),
|
||||
{
|
||||
"ASSET-ONLY": 65,
|
||||
"L4-USER-M-PROCESS": 1,
|
||||
"NON_MAIN_CLASS": 10,
|
||||
"UPSTREAM-DEMO": 1,
|
||||
},
|
||||
"remaining skip simulation implementation coverage kind counts drift",
|
||||
);
|
||||
|
||||
for (const row of parsedRows) {
|
||||
assert.equal(row.linuxcnc_source_available, "1", `${row.path}: LinuxCNC source path missing`);
|
||||
assert.equal(row.simulation_coverage_ready, "1", `${row.path}: skipped row lacks simulation coverage`);
|
||||
assert.equal(row.standalone_main_program_ready, "0", `${row.path}: skipped row must not claim standalone main readiness`);
|
||||
assert.equal(row.promotion_allowed, "0", `${row.path}: skipped row must not allow promotion`);
|
||||
assert.notEqual(row.simulation_implementation_mode, "unclassified_skip", `${row.path}: unclassified skip implementation mode`);
|
||||
assert.notEqual(row.recommended_next_step, "-", `${row.path}: skipped row lacks next implementation step`);
|
||||
|
||||
if (row.skip_kind === "ASSET-ONLY") {
|
||||
assert.equal(row.class, "remap_subroutine", `${row.path}: asset-only row should remain remap subroutine coverage`);
|
||||
assert.equal(
|
||||
row.simulation_implementation_mode,
|
||||
"source_asset_dependency",
|
||||
`${row.path}: asset-only implementation mode drift`,
|
||||
);
|
||||
} else if (row.skip_kind === "NON_MAIN_CLASS") {
|
||||
assert.equal(row.class, "macro_load", `${row.path}: non-main row should remain macro/load coverage`);
|
||||
assert.equal(
|
||||
row.simulation_implementation_mode,
|
||||
"macro_load_or_non_main_class_coverage",
|
||||
`${row.path}: non-main implementation mode drift`,
|
||||
);
|
||||
} else if (row.skip_kind === "L4-USER-M-PROCESS") {
|
||||
const audit = auditByPath.get(row.path);
|
||||
assert.ok(audit, `${row.path}: user-M skipped main row must have promotion audit`);
|
||||
assert.equal(row.path, "axis/vismach/millturn/example.ngc", `${row.path}: user-M path drift`);
|
||||
assert.equal(row.main_program_class, "1", `${row.path}: user-M row must remain main class`);
|
||||
assert.equal(row.runtime_promotion_blocked, "1", `${row.path}: user-M runtime promotion block drift`);
|
||||
assert.equal(
|
||||
row.simulation_implementation_status,
|
||||
"implemented_as_source_derived_boundary_state_proof_runtime_execution_blocked",
|
||||
`${row.path}: user-M implementation status drift`,
|
||||
);
|
||||
} else if (row.skip_kind === "UPSTREAM-DEMO") {
|
||||
const audit = auditByPath.get(row.path);
|
||||
assert.ok(audit, `${row.path}: upstream demo skipped main row must have promotion audit`);
|
||||
assert.equal(
|
||||
row.path,
|
||||
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_g533.ngc",
|
||||
`${row.path}: upstream demo path drift`,
|
||||
);
|
||||
assert.equal(row.native_expected_failure, "upstream-demo-missing-motion-gcode", `${row.path}: upstream demo failure drift`);
|
||||
assert.equal(row.main_program_class, "1", `${row.path}: upstream demo row must remain main class`);
|
||||
assert.equal(row.runtime_promotion_blocked, "1", `${row.path}: upstream demo runtime promotion block drift`);
|
||||
} else {
|
||||
assert.fail(`${row.path}: unexpected skip kind ${row.skip_kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
return parsedRows;
|
||||
}
|
||||
|
||||
for (const record of nativeRecords) {
|
||||
const skipReason = blockedKind(record, pathMatrixByPath);
|
||||
if (skipReason === "L4-PYTHON-REMAP" && promotedPythonRemapInventoryPaths.has(record.path)) {
|
||||
@@ -14191,6 +14411,40 @@ verifyRemainingSkipMainProgramPromotionAuditRows({
|
||||
summaryRows,
|
||||
promotionCandidateRows: promotionCandidateRecords,
|
||||
});
|
||||
const remainingSkipMainProgramPromotionAuditRecords = parseTsv(
|
||||
`${[
|
||||
"path",
|
||||
"ini",
|
||||
"current_status",
|
||||
"skip_kind",
|
||||
"class",
|
||||
"native_status",
|
||||
"native_expected_failure",
|
||||
"blocked_kind",
|
||||
"dependency_class",
|
||||
"simulation_proof_status",
|
||||
"native_pass_ready",
|
||||
"node_inventory_gate_complete",
|
||||
"browser_smoke_gate_complete",
|
||||
"promotion_lock_active",
|
||||
"promotion_ready",
|
||||
"promotion_allowed",
|
||||
"promotion_decision",
|
||||
"block_reason",
|
||||
"recommended_next_command",
|
||||
].join("\t")}\n${remainingSkipMainProgramPromotionAuditRowsGenerated.join("\n")}\n`,
|
||||
);
|
||||
const remainingSkipSimulationImplementationCoverageRowsGenerated =
|
||||
remainingSkipSimulationImplementationCoverageRows({
|
||||
summaryRows,
|
||||
boundaryRows: boundarySummaryRecords,
|
||||
remainingSkipAuditRows: remainingSkipMainProgramPromotionAuditRecords,
|
||||
});
|
||||
verifyRemainingSkipSimulationImplementationCoverageRows({
|
||||
rows: remainingSkipSimulationImplementationCoverageRowsGenerated,
|
||||
summaryRows,
|
||||
remainingSkipAuditRows: remainingSkipMainProgramPromotionAuditRecords,
|
||||
});
|
||||
const evidenceExpansionCandidateRowsGenerated = evidenceExpansionCandidateRows({
|
||||
summaryRows,
|
||||
boundaryRows: boundarySummaryRecords,
|
||||
@@ -14280,6 +14534,34 @@ writeFileSync(
|
||||
].join("\n")}\n`,
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
remainingSkipSimulationImplementationCoveragePath,
|
||||
`${[
|
||||
[
|
||||
"path",
|
||||
"ini",
|
||||
"skip_kind",
|
||||
"class",
|
||||
"native_status",
|
||||
"native_expected_failure",
|
||||
"linuxcnc_source_path",
|
||||
"linuxcnc_source_available",
|
||||
"simulation_implementation_mode",
|
||||
"simulation_implementation_status",
|
||||
"main_program_class",
|
||||
"standalone_main_program_ready",
|
||||
"dependency_class",
|
||||
"blocked_kind",
|
||||
"promotion_ready",
|
||||
"promotion_allowed",
|
||||
"runtime_promotion_blocked",
|
||||
"simulation_coverage_ready",
|
||||
"recommended_next_step",
|
||||
].join("\t"),
|
||||
...remainingSkipSimulationImplementationCoverageRowsGenerated,
|
||||
].join("\n")}\n`,
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
boundarySummaryPath,
|
||||
`${[
|
||||
|
||||
Reference in New Issue
Block a user