"""Reviewed changed-path to regression-test selection.""" from __future__ import annotations import fnmatch import json from pathlib import Path def string_list(value: object, field: str) -> list[str]: if not isinstance(value, list) or not all( isinstance(item, str) and item for item in value ): raise ValueError(f"{field} must be a non-empty string array") return list(value) def load_manifest(path: Path) -> dict[str, object]: try: value = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as error: raise ValueError(f"failed to read impact manifest {path}: {error}") from error if not isinstance(value, dict) or value.get("schema_version") != 1: raise ValueError("impact manifest schema_version must be 1") always = value.get("always") rules = value.get("rules") if not isinstance(always, list) or not isinstance(rules, list): raise ValueError("impact manifest always and rules must be arrays") seen_rule_ids: set[str] = set() for index, item in enumerate(always): if not isinstance(item, dict): raise ValueError(f"always[{index}] must be an object") if not isinstance(item.get("test"), str) or not item["test"]: raise ValueError(f"always[{index}].test must be a non-empty string") if not isinstance(item.get("rationale"), str) or not item["rationale"]: raise ValueError(f"always[{index}].rationale must be a non-empty string") for index, item in enumerate(rules): if not isinstance(item, dict): raise ValueError(f"rules[{index}] must be an object") rule_id = item.get("id") rationale = item.get("rationale") if not isinstance(rule_id, str) or not rule_id: raise ValueError(f"rules[{index}].id must be a non-empty string") if rule_id in seen_rule_ids: raise ValueError(f"duplicate impact rule id: {rule_id}") seen_rule_ids.add(rule_id) if not isinstance(rationale, str) or not rationale: raise ValueError(f"rules[{index}].rationale must be a non-empty string") string_list(item.get("globs"), f"rules[{index}].globs") string_list(item.get("tests"), f"rules[{index}].tests") string_list(value.get("fallback_globs"), "fallback_globs") return value def matches(path: str, patterns: list[str]) -> bool: return any(fnmatch.fnmatchcase(path, pattern) for pattern in patterns) def select_tests(changed_paths: list[str], manifest_path: Path) -> dict[str, object]: manifest = load_manifest(manifest_path) always = manifest.get("always") rules = manifest.get("rules") if not isinstance(always, list) or not isinstance(rules, list): raise ValueError("impact manifest always and rules must be arrays") reasons: dict[str, set[str]] = {} for item in always: if isinstance(item, dict): reasons.setdefault(str(item["test"]), set()).add(str(item["rationale"])) matched_rules: list[dict[str, object]] = [] matched_paths: set[str] = set() for rule in rules: if not isinstance(rule, dict): continue globs = string_list(rule["globs"], f"rule {rule['id']} globs") paths = sorted(path for path in set(changed_paths) if matches(path, globs)) if not paths: continue matched_paths.update(paths) reason = f"{rule['id']}: {rule['rationale']}" for test in string_list(rule["tests"], f"rule {rule['id']} tests"): reasons.setdefault(test, set()).add(reason) matched_rules.append({"id": rule["id"], "paths": paths}) fallback_globs = string_list(manifest["fallback_globs"], "fallback_globs") unmapped_paths = sorted( path for path in set(changed_paths) if path not in matched_paths and matches(path, fallback_globs) ) return { "schema_version": 1, "manifest": manifest_path.as_posix(), "changed_paths": sorted(set(changed_paths)), "full_suite": bool(unmapped_paths), "fallback_reasons": ( ["unmapped production/build/test paths require the full suite"] if unmapped_paths else [] ), "unmapped_paths": unmapped_paths, "matched_rules": sorted(matched_rules, key=lambda item: str(item["id"])), "selected_tests": [ {"name": name, "reasons": sorted(values)} for name, values in sorted(reasons.items()) ], }