"""DGXC discovery verifier — exercise the new recursive discover_assets against four synthetic layouts. Self-contained: imports validate.py with SIMREADY_INSIDE_KIT + SIMREADY_FOUNDATIONS_PATH already set so the bootstrap gate clears. Usage on the pod: export SIMREADY_FOUNDATIONS_PATH=/home/horde/.simready/simready_foundations /home/horde/.simready/venv/bin/python3 test_discover_dgxc.py """ from __future__ import annotations import os import sys import tempfile from pathlib import Path # Skip the Kit re-exec gate (we're testing pure-Python discovery, no Kit). os.environ.setdefault("SIMREADY_INSIDE_KIT", "1") # Resolve the repo: env var first (DGXC ad-hoc run), then assume the # script lives inside a checkout (workflow / local). _env_repo = os.environ.get("SIMREADY_REPO") if _env_repo: REPO = Path(_env_repo) else: REPO = Path(__file__).resolve().parents[2] SKILL = REPO / "tools" / "validation" / "plugins" / "simready-report" / "skills" / "simready-report" if not SKILL.is_dir(): raise SystemExit( f"Validator skill dir not found at {SKILL}. " f"Set SIMREADY_REPO to the simready-oem-library-pm checkout root." ) sys.path.insert(0, str(SKILL)) import validate as v # noqa: E402 CASES = [ # (label, build_tree, expected_assets, expected_finding_codes) ("well_shaped", lambda root: [ (root / "well_shaped" / "well_shaped.usda", "interface"), (root / "well_shaped" / "Geometry" / "mesh.usda", "sublayer (Geometry)"), (root / "well_shaped" / "Materials" / "mat.usda", "sublayer (Materials)"), ], # Old behavior preserved: only the bundle interface is returned. ["well_shaped/well_shaped.usda"], []), ("flat", lambda root: [ (root / "flat" / "scene_a.usda", "root-level USD, no bundle"), (root / "flat" / "scene_b.usda", "root-level USD, no bundle"), ], # New behavior: both found, both tagged as non-standard. ["flat/scene_a.usda", "flat/scene_b.usda"], ["LAYOUT.NON_STANDARD_BUNDLE", "LAYOUT.NON_STANDARD_BUNDLE"]), ("deep", lambda root: [ (root / "deep" / "team1" / "v1" / "scenes" / "render_target.usda", "deeply-nested USD, no bundle wrapper"), ], # New behavior: found at depth, flagged. ["deep/team1/v1/scenes/render_target.usda"], ["LAYOUT.NON_STANDARD_BUNDLE"]), ("mixed", lambda root: [ (root / "mixed" / "proper_bundle" / "proper_bundle.usda", "interface"), (root / "mixed" / "proper_bundle" / "Geometry" / "g.usda", "sublayer"), (root / "mixed" / "orphan.usda", "standalone — not a bundle"), ], # Bundle interface and the orphan validate; sublayer excluded; orphan flagged. ["mixed/orphan.usda", "mixed/proper_bundle/proper_bundle.usda"], ["LAYOUT.NON_STANDARD_BUNDLE"]), # Real-world layout from Aperdata's Kitchen-01 dataset: # `/.usd` for the top-level interface plus a # `/SubUSDs/.usd` sublayer tree. The interface # stem (`Indoor`) doesn't match the dir name (`0_Kitchen_Indoor`), # so older logic missed the SubUSDs filter and counted them as # standalone assets — Aperdata went from 23 expected to 933 # validated. New logic excludes anything under a SUBLAYER_DIR # ancestor structurally, regardless of bundle naming. ("aperdata_kitchen", lambda root: [ (root / "aperdata_kitchen" / "0_Kitchen_Indoor" / "Indoor.usda", "interface (stem != parent dir)"), (root / "aperdata_kitchen" / "0_Kitchen_Indoor" / "SubUSDs" / "CL06.usda", "sublayer (under SubUSDs)"), (root / "aperdata_kitchen" / "0_Kitchen_Indoor" / "SubUSDs" / "CL06_1.usda", "sublayer (under SubUSDs)"), (root / "aperdata_kitchen" / "0_Kitchen_Indoor" / "SubUSDs" / "DEC_large_leaf.usda", "sublayer (under SubUSDs)"), (root / "aperdata_kitchen" / "mug_handheld" / "Collected_dining_mug_handheld" / "dining_mug_handheld.usda", "interface in nested Collected_* dir"), ], # Only the two interface USDs validate. SubUSDs/* are excluded # structurally. The deep mug_handheld interface IS the only # candidate in its top-level dir, so it counts as a bundle # interface (no layout warning). Indoor.usd is also the only # candidate under 0_Kitchen_Indoor → no warning. [ "aperdata_kitchen/0_Kitchen_Indoor/Indoor.usda", "aperdata_kitchen/mug_handheld/Collected_dining_mug_handheld/dining_mug_handheld.usda", ], []), ] def _normalize(paths: list[Path], root: Path) -> list[str]: return sorted(str(p.relative_to(root)).replace("\\", "/") for p in paths) def main() -> int: failures = 0 with tempfile.TemporaryDirectory(prefix="sr-discover-test-") as td: root = Path(td) for label, build, expected_assets, expected_codes in CASES: case_dir = root / label case_dir.mkdir(parents=True, exist_ok=True) for usd_path, _why in build(root): usd_path.parent.mkdir(parents=True, exist_ok=True) usd_path.write_text("()") # empty stage — discover_assets is pure FS walk assets, findings = v.discover_assets(case_dir) got_assets = _normalize(assets, root) got_codes = sorted([f["code"] for f in findings]) exp_assets = sorted(expected_assets) exp_codes = sorted(expected_codes) ok = (got_assets == exp_assets and got_codes == exp_codes) mark = "PASS" if ok else "FAIL" print(f" [{mark}] {label}") print(f" expected assets: {exp_assets}") print(f" got assets: {got_assets}") print(f" expected findings: {exp_codes}") print(f" got findings: {got_codes}") if not ok: failures += 1 for f in findings: print(f" finding detail: {f}") print() if failures: print(f"{failures} case(s) FAILED") return 1 print("all cases passed — recursive discovery + layout findings working end-to-end on DGXC") return 0 if __name__ == "__main__": raise SystemExit(main())