Files
govoplan/tools/devkit/govoplan_devkit/coverage.py
T
zemion 2ffdb23f69
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 11m30s
feat(devkit): add resumable workspace automation and UI review tooling
Verified with the coordinated workspace changes by devkit full run
2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed).
This shared UI pass does not mark the individual module reviews complete.
2026-09-09 02:03:17 +02:00

454 lines
18 KiB
Python
Executable File

"""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,
],
}