PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
audit_user_facing_reporting.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Validate the repository's option-aware user-facing reporting contract."""
3
4from __future__ import annotations
5
6import re
7import sys
8import json
9from pathlib import Path
10
11
12REPO_ROOT = Path(__file__).resolve().parents[2]
13SRC_DIR = REPO_ROOT / "src"
14ALLOWED_DIRECT_C_OUTPUT = {
15 "io.c", # Rank-zero effective-configuration banner.
16 "logging.c", # Logging and diagnostic rendering sinks.
17 "setup.c", # Bootstrap warnings before logging is configured.
18 "runloop.c", # Progress-bar line control.
19 "postprocessor.c", # Progress-bar line control.
20 "momentumsolvers.c", # Unconditional convergence warnings.
21}
22BANNER_REQUIRED = (
23 "Momentum Equation Solver",
24 "Initial Pseudo-CFL (Courant)",
25 "Newton-Krylov PETSc Controls",
26 "Pseudo-Time Controller : NOT APPLICABLE",
27 "Console Log Level",
28 "Profiling Timestep Output",
29 "Runtime Memory Log",
30 "Solution Convergence Mode",
31)
32CONTRACT_PATH = REPO_ROOT / "tests" / "tooling" / "user_facing_reporting_contract.json"
33CLI_REQUIRED = ("Momentum Solver: Dual Time Picard Jameson RK", "Momentum Solver: Newton Krylov",
34 "Momentum Solver: Explicit RK (no pseudo-time controller)",
35 "Dual-Time Pseudo-Time Controls", "Newton--Krylov Controls")
36
37
38def strip_c_comments(text: str) -> str:
39 """!
40 @brief Replace C comments while preserving source line positions.
41 @param[in] text C source text to normalize.
42 @return Source text with comments replaced by whitespace/newlines.
43 """
44
45 text = re.sub(r"/\*.*?\*/", lambda match: "\n" * match.group(0).count("\n"), text, flags=re.DOTALL)
46 return re.sub(r"//[^\n]*", "", text)
47
48
49def audit_direct_c_output() -> list[str]:
50 """!
51 @brief Return raw C console emissions outside their approved rendering modules.
52 @return Human-readable findings for every disallowed emission.
53 """
54
55 findings: list[str] = []
56 pattern = re.compile(r"\b(?:PetscPrintf|PetscSynchronizedPrintf|printf)\s*\‍(")
57 for path in sorted(SRC_DIR.glob("*.c")):
58 if path.name in ALLOWED_DIRECT_C_OUTPUT:
59 continue
60 stripped = strip_c_comments(path.read_text(encoding="utf-8", errors="ignore"))
61 for match in pattern.finditer(stripped):
62 line = stripped.count("\n", 0, match.start()) + 1
63 findings.append(
64 f"{path.relative_to(REPO_ROOT)}:{line}: raw console output must use the logging policy "
65 "or be moved to an approved reporting surface."
66 )
67 return findings
68
69
70def require_fragments(path: Path, fragments: tuple[str, ...], surface: str) -> list[str]:
71 """!
72 @brief Return missing reporting-contract fragments for one user-facing surface.
73 @param[in] path Source file containing the surface.
74 @param[in] fragments Required literal contract fragments.
75 @param[in] surface Human-readable surface name for failures.
76 @return Findings for missing fragments.
77 """
78
79 text = path.read_text(encoding="utf-8")
80 return [f"{surface}: missing required option-aware reporting fragment {fragment!r}."
81 for fragment in fragments if fragment not in text]
82
83
84def audit_cli_contract(contract: dict) -> list[str]:
85 """!
86 @brief Verify that every declared CLI command has a parser, dispatch handler, and contextual message.
87 @param[in] contract Parsed user-facing reporting contract mapping.
88 @return Findings for missing CLI command-reporting contract elements.
89 """
90
91 cli_text = (REPO_ROOT / "picurv_cli" / "cli.py").read_text(encoding="utf-8")
92 core_text = (REPO_ROOT / "picurv_cli" / "core.py").read_text(encoding="utf-8")
93 findings: list[str] = []
94 for command, entry in contract["cli_commands"].items():
95 for field, text, surface in (
96 ("parser", cli_text, "CLI parser"),
97 ("handler", core_text, "CLI handler"),
98 ("context", cli_text + core_text, "CLI context"),
99 ):
100 if entry[field] not in text:
101 findings.append(f"{surface} for '{command}': missing contract fragment {entry[field]!r}.")
102 return findings
103
104
105def main() -> int:
106 """!
107 @brief Audit C banner, CLI summaries, and direct-output routing.
108 @return Process status code.
109 """
110
111 findings = audit_direct_c_output()
112 contract = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))
113 findings.extend(require_fragments(
114 REPO_ROOT / contract["banner"]["source"], tuple(contract["banner"]["required_fragments"]), "C startup banner"
115 ))
116 findings.extend(require_fragments(REPO_ROOT / "picurv_cli" / "core.py", CLI_REQUIRED, "Python CLI/summarize"))
117 findings.extend(audit_cli_contract(contract))
118 if findings:
119 print("\n".join(findings), file=sys.stderr)
120 return 1
121 print("User-facing reporting audit passed.")
122 return 0
123
124
125if __name__ == "__main__":
126 raise SystemExit(main())
list[str] require_fragments(Path path, tuple[str,...] fragments, str surface)
Return missing reporting-contract fragments for one user-facing surface.
int main()
Audit C banner, CLI summaries, and direct-output routing.
list[str] audit_cli_contract(dict contract)
Verify that every declared CLI command has a parser, dispatch handler, and contextual message.
str strip_c_comments(str text)
Replace C comments while preserving source line positions.
list[str] audit_direct_c_output()
Return raw C console emissions outside their approved rendering modules.