PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
audit_ingress.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3Static ingress audit for PETSc option parsing in setup/io.
4
5This script scans src/setup.c and src/io.c for PetscOptionsGet*/HasName calls,
6extracts option flags, and compares them against a maintained manifest.
7"""
8
9from __future__ import annotations
10
11import argparse
12import json
13import pathlib
14import re
15import sys
16from typing import Iterable, Set
17
18
19OPTION_RE = re.compile(
20 r'PetscOptions(?:Get(?:Int|Real|Bool|String|IntArray|RealArray)|HasName)\s*'
21 r'\‍(\s*NULL\s*,\s*NULL\s*,\s*"(-[^"]+)"'
22)
23
24
25def scan_petsc_options(paths: Iterable[pathlib.Path]) -> Set[str]:
26 flags: Set[str] = set()
27 for path in paths:
28 text = path.read_text(encoding="utf-8")
29 for match in OPTION_RE.finditer(text):
30 flags.add(match.group(1))
31 return flags
32
33
34def load_manifest(path: pathlib.Path) -> Set[str]:
35 data = json.loads(path.read_text(encoding="utf-8"))
36 options = data.get("known_petsc_options")
37 if not isinstance(options, list):
38 raise ValueError("Manifest key 'known_petsc_options' must be a list.")
39 bad = [opt for opt in options if not isinstance(opt, str) or not opt.startswith("-")]
40 if bad:
41 raise ValueError(f"Manifest has invalid option entries: {bad}")
42 return set(options)
43
44
45def main() -> int:
46 parser = argparse.ArgumentParser(description="Audit PETSc option ingress against manifest.")
47 parser.add_argument(
48 "--manifest",
49 default="scripts/audit_ingress_manifest.json",
50 help="Path to ingress manifest JSON.",
51 )
52 parser.add_argument(
53 "--show-scanned",
54 action="store_true",
55 help="Print scanned option list before comparison.",
56 )
57 args = parser.parse_args()
58
59 repo_root = pathlib.Path(__file__).resolve().parents[1]
60 manifest_path = (repo_root / args.manifest).resolve()
61 scan_paths = [repo_root / "src/setup.c", repo_root / "src/io.c"]
62
63 if not manifest_path.exists():
64 print(f"[ERROR] Manifest not found: {manifest_path}", file=sys.stderr)
65 return 2
66
67 scanned = scan_petsc_options(scan_paths)
68 expected = load_manifest(manifest_path)
69
70 missing_in_manifest = sorted(scanned - expected)
71 stale_in_manifest = sorted(expected - scanned)
72
73 if args.show_scanned:
74 print("[INFO] Scanned PETSc options:")
75 for flag in sorted(scanned):
76 print(flag)
77 print("")
78
79 print(f"[INFO] Scanned options: {len(scanned)}")
80 print(f"[INFO] Manifest options: {len(expected)}")
81
82 if missing_in_manifest:
83 print("[ERROR] New PETSc ingress options missing in manifest:")
84 for flag in missing_in_manifest:
85 print(f" - {flag}")
86
87 if stale_in_manifest:
88 print("[ERROR] Manifest options no longer present in setup/io scan:")
89 for flag in stale_in_manifest:
90 print(f" - {flag}")
91
92 if missing_in_manifest or stale_in_manifest:
93 print(
94 "[FAIL] Ingress drift detected. Update scripts/audit_ingress_manifest.json and docs mapping.",
95 file=sys.stderr,
96 )
97 return 1
98
99 print("[OK] Ingress manifest matches setup/io PETSc option scan.")
100 return 0
101
102
103if __name__ == "__main__":
104 raise SystemExit(main())
Set[str] scan_petsc_options(Iterable[pathlib.Path] paths)
Set[str] load_manifest(pathlib.Path path)