"""Explicit suite-plan coverage; never execute or guess nested shell/npm flows.""" from __future__ import annotations import hashlib import os from pathlib import Path import re import shlex import stat from .common import digest, redact_argv, reject_symlinks from .package_tests import CORE_COMPONENT_SUITES, declared_tests, discovered_sources from .workspace import load_project DISPOSITIONS = ("planned", "covered_elsewhere", "excluded", "unsupported") MAX_SUITES = 4096 def _read_canonical(path: Path) -> str: reject_symlinks(path) descriptor = os.open( path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) ) with os.fdopen(descriptor, "rb") as handle: metadata = os.fstat(handle.fileno()) if not stat.S_ISREG(metadata.st_mode) or metadata.st_size > 1024 * 1024: raise ValueError("Canonical focused gate must be a bounded regular file") encoded = handle.read(1024 * 1024 + 1) if len(encoded) > 1024 * 1024: raise ValueError("Canonical focused gate exceeds its size bound") return encoded.decode("utf-8") def _node_argv(argv: list[str], cwd: Path) -> tuple[str, ...] | None: if ( not argv or argv[0] not in {"node", "{node}", "$NODE"} and Path(argv[0]).name not in {"node", "nodejs"} ): return None offset = 2 if len(argv) > 1 and argv[1] == "--test" else 1 if len(argv) != offset + 1: return None target = Path(argv[offset]) if "$" in str(target): return None return ("node", *argv[1:offset], str((cwd / target).resolve())) def canonical_invocations(workspace_root: Path, meta: Path, core: Path) -> dict: """Recognize direct commands in exact registered phase wrappers or legacy top level. Shell is not evaluated. Bodies, conditionals, loops, functions, npm hooks and recursive commands do not grant coverage. Unknown cwd invalidates matches. The registered marked wrappers are the only function bodies admitted. """ path = meta / "tools/checks/check-focused.sh" try: text = _read_canonical(path) except (OSError, ValueError): return { "path": str(path), "sha256": None, "npm": [], "node": [], "node_phases": [], "notes": [ "Canonical focused script is unavailable/unreadable; no package coverage inferred." ], } values = { "WORKSPACE_ROOT": str(workspace_root), "META_ROOT": str(meta), "ROOT": str(core), } def expand(value: str) -> str | None: value = re.sub( r"\$\{(WORKSPACE_ROOT|META_ROOT|ROOT)\}|\$(WORKSPACE_ROOT|META_ROOT|ROOT)(?![A-Za-z0-9_])", lambda match: values[match[1] or match[2]], value, ) return None if "$" in value or "`" in value else value result = { "path": str(path), "sha256": hashlib.sha256(text.encode()).hexdigest(), "npm": [], "node": [], "node_phases": [], "notes": [], } metadata = meta / "tools/checks/focused-phases.json" blocks = {None: text} if metadata.exists() or metadata.is_symlink() or "# devkit-phase:" in text: from .catalog import focused_phases, focused_phase_bodies try: phases = focused_phases(meta) blocks = focused_phase_bodies(text, phases) result["phase_ids"] = [phase["id"] for phase in phases] result["metadata_sha256"] = digest(phases) except (OSError, ValueError): result["notes"].append( "Focused phase metadata/marked bodies are invalid or unavailable; no phase coverage inferred." ) return result cwd: Path | None = meta body_end, nesting = None, 0 current_phase = object() for phase_id, line in ( (phase_id, line) for phase_id, block in blocks.items() for line in block.replace("\\\n", " ").splitlines() ): if current_phase != phase_id: cwd, body_end, nesting = meta, None, 0 current_phase = phase_id stripped = line.strip() if body_end is not None: if stripped == body_end: body_end = None continue heredoc = re.search(r"<<-?\s*['\"]?([A-Za-z_][A-Za-z0-9_]*)['\"]?", line) if heredoc: body_end = heredoc[1] continue if not stripped or stripped.startswith("#"): continue if re.match( r"(?:if|for|while|until|case|select)\b|(?:function\s+\w+|\w+\s*\(\s*\))", stripped, ): nesting += 1 continue if re.match(r"(?:fi|done|esac)\b|^}\s*;?$", stripped): nesting = max(0, nesting - 1) continue if nesting: if re.search(r"\bcd\s", stripped): cwd = None continue try: parts = shlex.split(line, comments=True) except ValueError: result["notes"].append( "An unparseable shell line grants no suite coverage." ) continue if not parts: continue if parts[0] == "cd": destination = expand(parts[1]) if len(parts) == 2 else None candidate = ( (cwd / destination).resolve() if destination and cwd else Path(destination).resolve() if destination and Path(destination).is_absolute() else None ) cwd = ( candidate if candidate and candidate.is_relative_to(workspace_root) else None ) continue if cwd is None: continue if ( parts[0] in {"$NPM", "${NPM}", "npm"} and len(parts) >= 3 and parts[1] == "run" and re.fullmatch(r"test(?::[A-Za-z0-9_.:-]+)?", parts[2]) ): arguments = parts[3:] if arguments and ( arguments[0] != "--" or any( not re.fullmatch(r"[A-Za-z0-9_.-]+", item) for item in arguments[1:] ) ): continue result["npm"].append( { "cwd": str(cwd), "name": parts[2], "args": arguments[1:] if arguments else [], "phase": phase_id, } ) elif parts[0] in {"$NODE", "${NODE}", "node"}: expanded = [expand(part) for part in parts[1:]] if all(part is not None for part in expanded): command = _node_argv(["node", *expanded], cwd) if command: result["node"].append(command) result["node_phases"].append({"argv": command, "phase": phase_id}) result["notes"] = list(dict.fromkeys(result["notes"]))[:8] return result def coverage_inventory( workspace_root: Path, profile: str, project_file: Path | None, stages: list[dict] ) -> dict: workspace_root = workspace_root.resolve() project = load_project(workspace_root, project_file) stage_map = {item["id"]: item for item in stages} direct = {} explicit_npm = {} for stage in stages: argv, cwd = stage["argv"], Path(stage["cwd"]) normalized = _node_argv(argv, cwd) if normalized: direct[normalized] = stage["id"] if ( len(argv) == 3 and (argv[0] in {"npm", "{npm}"} or Path(argv[0]).name == "npm") and argv[1] == "run" ): explicit_npm[(str(cwd.resolve()), argv[2])] = stage["id"] paths = {repo.name: repo.path for repo in project.repositories} canonical = ( canonical_invocations( workspace_root, paths.get("govoplan", workspace_root / "govoplan"), paths.get("govoplan-core", workspace_root / "govoplan-core"), ) if project_file is None else None ) canonical_stages = {} if canonical is not None: legacy = stage_map.get("focused-workspace") if legacy and legacy["argv"] == ["bash", canonical["path"]]: canonical_stages[None] = "focused-workspace" for identity in canonical.get("phase_ids", []): check = stage_map.get("focused." + identity) if check and check["argv"] == [ "bash", canonical["path"], "--phase", identity, ]: canonical_stages[identity] = check["id"] full = bool(canonical_stages) invocations = ( [ {**item, "covering_stage": canonical_stages[item.get("phase")]} for item in canonical["npm"] if item.get("phase") in canonical_stages ] if canonical else [] ) canonical_nodes = ( { tuple(item["argv"]): canonical_stages[item.get("phase")] for item in canonical.get("node_phases", []) if item.get("phase") in canonical_stages } if canonical else {} ) suites, notes = [], [] if canonical: notes.extend(canonical["notes"]) core_package = ( paths.get("govoplan-core", workspace_root / "govoplan-core") / "webui/package.json" ) core_requests = [ item for item in invocations if item["cwd"] == str(core_package.parent) and item["name"] == "test:components" ] requested_components = set() for item in core_requests: requested_components.update( CORE_COMPONENT_SUITES if not item["args"] or item["args"] == ["all"] else item["args"] ) requested_components.intersection_update(CORE_COMPONENT_SUITES) for repo in project.repositories: for location in ("package.json", "webui/package.json"): package = repo.path / location if not package.exists() and not package.is_symlink(): continue entries = declared_tests(repo, package) if location == "webui/package.json": declared_argv = { tuple(item["_argv"]) for item in entries if item["_argv"] } entries.extend( item for item in discovered_sources(repo) if not item["_argv"] or tuple(item["_argv"]) not in declared_argv ) for item in entries: private_argv = item.pop("_argv") component = item["component_suite"] identity = (str(package.parent), item["name"]) normalized = ( _node_argv(private_argv, package.parent) if private_argv else None ) matching = [ value for value in invocations if (value["cwd"], value["name"]) == identity ] item.update(disposition="excluded", covering_stage=None) if component is not None: all_or_selected = ( component == "all" or component in requested_components ) if "core.component-batch" in stage_map: item.update( disposition="planned" if component == "all" else "covered_elsewhere", covering_stage="core.component-batch", reason="UI explicitly runs the shared component batch; quick never compiles it.", ) elif full and core_requests and all_or_selected: item.update( disposition="planned" if component == "all" else "covered_elsewhere", covering_stage=core_requests[0]["covering_stage"], reason=f"Canonical focused gate explicitly selects {len(requested_components)}/{len(CORE_COMPONENT_SUITES)} component suites; this is not the complete component batch.", ) else: item["reason"] = ( "No component compilation in quick/backend; UI runs all components. Full runs only its explicitly named subset." ) if component == "all": selected = ( list(CORE_COMPONENT_SUITES) if "core.component-batch" in stage_map else sorted(requested_components) ) item.update( covered_components=selected, excluded_components=[ name for name in CORE_COMPONENT_SUITES if name not in selected ], ) elif identity in explicit_npm: item.update( disposition="planned", covering_stage=explicit_npm[identity], reason="Explicit project check invokes this exact package suite.", ) elif matching: if any(not value["args"] for value in matching): item.update( disposition="planned", covering_stage=next( value["covering_stage"] for value in matching if not value["args"] ), reason="The canonical focused script directly invokes this exact package suite.", ) else: item["reason"] = ( "Canonical invocation supplies arguments; complete suite coverage cannot be inferred." ) elif normalized in direct: item.update( disposition="planned", covering_stage=direct[normalized], reason="A selected stage runs this exact direct source-test command.", ) elif normalized in canonical_nodes: item.update( disposition="covered_elsewhere", covering_stage=canonical_nodes[normalized], reason="Canonical focused script directly runs this suite's exact Node target.", ) elif private_argv is None: item["disposition"] = "unsupported" elif item["name"] in { "test:module-permutations", "test:vite-cache-isolation", }: item["reason"] = ( "Separate environment/permutation suite; absent from the selected stage plan." ) else: item["reason"] = ( "Not directly present in this profile's stage plan; nested npm scripts/hooks are not inferred." ) suites.append(item) if len(suites) > MAX_SUITES: raise ValueError("Coverage inventory exceeds its bounded suite count") if project_file is not None: for check in project.config.get("checks", []): identity = check["id"] suites.append( { "repo": ",".join(check.get("repos", [])) or "project", "name": identity, "kind": "project-check", "package_path": None, "argv": redact_argv(check["argv"]), "command_sha256": digest(check["argv"]), "disposition": "planned" if identity in stage_map else "excluded", "covering_stage": identity if identity in stage_map else None, "reason": "Selected declared check or dependency." if identity in stage_map else "Declared check is outside this profile/selection.", } ) if len(suites) > MAX_SUITES: raise ValueError("Coverage inventory exceeds its bounded suite count") counts = { kind: sum(item["disposition"] == kind for item in suites) for kind in DISPOSITIONS } notes.append( "Coverage describes planned suite invocations, not passing tests, per-test coverage, or completed UI review." ) if full: notes.append( "Full is the canonical focused gate, not every declared package test; recursive commands and npm hooks are deliberately not guessed." ) return { "schema_version": 1, "profile": profile, "scope": "Declared root/webui package test scripts, discovered UI structural checks, and custom project checks across registered repositories.", "suite_count": len(suites), "stages": list(stage_map), "counts": counts, "suites": suites, "canonical_gate": { key: canonical[key] for key in ("path", "sha256", "metadata_sha256") if key in canonical } if canonical else None, "notes": notes, "summary": [ "Suite coverage: " + ", ".join(f"{value} {key}" for key, value in counts.items()), *notes, ], }