Advance simulation parity and 3-axis tutorial coverage
This commit is contained in:
@@ -6,6 +6,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -17,6 +18,12 @@ HAL_REPORT = Path("coverage/hal-component-manifest.report.json")
|
||||
OUTPUT = Path("web/workspace/sim-case-catalog.mjs")
|
||||
REPORT = Path("coverage/sim-case-catalog.report.json")
|
||||
SCHEMA = "linuxcnc-sim-case-catalog.v1"
|
||||
RUNTIME_FIXTURE_FILES = (
|
||||
"linuxcnc/configs/sim/axis/minimal_xyz_cmds.hal",
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz_cmds.hal",
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_cmds.hal",
|
||||
"linuxcnc/configs/sim/axis/ja_tests/xz/xz_both_auto_cmds.hal",
|
||||
)
|
||||
|
||||
|
||||
def canonical(value: Any) -> str:
|
||||
@@ -32,6 +39,44 @@ def artifact(root: Path, path: str) -> dict[str, Any]:
|
||||
return {"path": path, "status": "missing"} if not value.is_file() else {"path": path, "bytes": value.stat().st_size, "sha256": sha256(value.read_bytes()), "status": "present"}
|
||||
|
||||
|
||||
def source_files(root: Path, directory: Path) -> list[Path]:
|
||||
"""Index source files without native runtime outputs.
|
||||
|
||||
Native LinuxCNC creates ignored parameter files, backups, bytecode, and
|
||||
generated HAL fixtures in the source tree. A filesystem glob would make
|
||||
the browser manifest depend on the last native run, so use the submodule's
|
||||
tracked set whenever Git metadata is available. The small set of
|
||||
LinuxCNC runtime HAL fixtures is added explicitly below because those
|
||||
files are consumed by the loader's existing runtime-fixture contract.
|
||||
"""
|
||||
base = root / directory
|
||||
git_root = root / "linuxcnc"
|
||||
if (git_root / ".git").exists():
|
||||
prefix = directory.as_posix().removeprefix("linuxcnc/")
|
||||
tracked_result = subprocess.run(
|
||||
["git", "-C", str(git_root), "ls-files", "--cached", "--", prefix],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
tracked = set(tracked_result.stdout.splitlines())
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(git_root), "ls-files", "--cached", "--others", "--exclude-standard", "--", prefix],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return [
|
||||
git_root / line
|
||||
for line in result.stdout.splitlines()
|
||||
# Native startup leaves empty tool.tbl files behind; only tracked
|
||||
# tables are source inputs, while missing tables are materialized
|
||||
# by the Web loader at their configured path.
|
||||
if (git_root / line).is_file() and (not line.endswith(".tbl") or line in tracked)
|
||||
]
|
||||
return [path for path in sorted(base.rglob("*")) if path.is_file() and "__pycache__" not in path.parts and path.suffix != ".pyc"]
|
||||
|
||||
|
||||
def ini_summary(path: Path) -> dict[str, str | None]:
|
||||
section = ""
|
||||
values: dict[str, str] = {}
|
||||
@@ -61,7 +106,7 @@ def generate(root: Path, output: Path, report_output: Path = REPORT) -> dict[str
|
||||
if path.startswith("linuxcnc/configs/sim/"):
|
||||
matrix_by_path.setdefault(path, row)
|
||||
cases = []
|
||||
for path in sorted((root / SIM_ROOT).rglob("*.ini")):
|
||||
for path in sorted(path for path in source_files(root, SIM_ROOT) if path.suffix == ".ini"):
|
||||
relative = path.relative_to(root).as_posix()
|
||||
row = matrix_by_path.get(relative, {})
|
||||
summary = ini_summary(path)
|
||||
@@ -81,12 +126,13 @@ def generate(root: Path, output: Path, report_output: Path = REPORT) -> dict[str
|
||||
"unresolved_count": len(row.get("unresolved_resources", [])),
|
||||
"source_sha256": sha256(path.read_bytes()),
|
||||
})
|
||||
file_index = [path.relative_to(root).as_posix() for path in sorted((root / SIM_ROOT).rglob("*")) if path.is_file()]
|
||||
file_index = [path.relative_to(root).as_posix() for path in sorted(source_files(root, SIM_ROOT))]
|
||||
for support_root in SUPPORT_ROOTS:
|
||||
file_index.extend(path.relative_to(root).as_posix() for path in sorted((root / support_root).rglob("*")) if path.is_file())
|
||||
file_index.extend(path.relative_to(root).as_posix() for path in sorted(source_files(root, support_root)))
|
||||
shared_asset = root / "linuxcnc/linuxcnc.gif"
|
||||
if shared_asset.is_file():
|
||||
file_index.append(shared_asset.relative_to(root).as_posix())
|
||||
file_index.extend(path for path in RUNTIME_FIXTURE_FILES if (root / path).is_file())
|
||||
file_index = sorted(set(file_index))
|
||||
hal_report = json.loads((root / HAL_REPORT).read_text(encoding="utf-8"))
|
||||
hal_components = sorted({str(row["component"]) for row in hal_report.get("component_manifest", []) if row.get("component")})
|
||||
|
||||
Reference in New Issue
Block a user