PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
Functions | Variables
audit_ingress Namespace Reference

Functions

str canonical_family (Iterable[str] fragments)
 Join the literal fragments of a constructed name into one stable pattern.
 
"dict[str, Set[str]]" extract_families (str text)
 Map each name variable built by PetscSNPrintf to every family it carries.
 
"tuple[Set[str], list[str]]" scan_option_families (Iterable[pathlib.Path] paths)
 Collect declared option families and any constructed name that lacks one.
 
Set[str] scan_petsc_options (Iterable[pathlib.Path] paths)
 Perform scan petsc options.
 
dict load_manifest (pathlib.Path path)
 Read and validate the option names, families, and sources in the manifest.
 
int main ()
 Entry point for this script.
 

Variables

str _ACCESSOR = r'PetscOptions(?:Get(?:Int|Real|Bool|String|IntArray|RealArray)|HasName)'
 
 OPTION_RE = re.compile(_ACCESSOR + r'\s*\‍(\s*NULL\s*,\s*NULL\s*,\s*"(-[^"]+)"')
 
 CONSTRUCTED_RE
 
 SNPRINTF_RE = re.compile(r'PetscSNPrintf\s*\‍(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,')
 
 LITERAL_RE = re.compile(r'"((?:[^"\\]|\\.)*)"')
 

Detailed Description

Static ingress audit for PETSc option parsing in setup/io.

This script scans the source files named by the manifest for PetscOptionsGet*/HasName
calls, extracts option flags, and compares them against a maintained manifest.

Two kinds of option name reach C. Most are string literals. A variable-arity
configuration, such as the field-statistics window list, must construct its names
instead, and a constructed name is invisible to a literal scan. Rather than let that
become a hole, this audit also collects the format strings those names are built
from, requires each to be declared as a family, and fails on any constructed name it
cannot trace back to one. The invariant is that every option name reaching C is
either a declared literal or a declared family.

Reads that pass a private PetscOptions object rather than NULL are deliberately out
of scope: they read checkpoint state, not user configuration.

Function Documentation

◆ canonical_family()

str audit_ingress.canonical_family ( Iterable[str]  fragments)

Join the literal fragments of a constructed name into one stable pattern.

Each substituted value collapses to a bare %, so a name spliced around PetscInt_FMT and one written with d produce the same family.

Parameters
[in]fragmentsLiteral chunks of the format expression, in source order.
Returns
Value returned by canonical_family().

Definition at line 46 of file audit_ingress.py.

46def canonical_family(fragments: Iterable[str]) -> str:
47 """!
48 @brief Join the literal fragments of a constructed name into one stable pattern.
49 @details Each substituted value collapses to a bare `%`, so a name spliced around
50 `PetscInt_FMT` and one written with `%d` produce the same family.
51 @param[in] fragments Literal chunks of the format expression, in source order.
52 @return Value returned by `canonical_family()`.
53 """
54 joined = "".join(fragments)
55 return re.sub(r"%[-+ #0-9.]*[a-zA-Z]?", "%", joined)
56
57
Here is the caller graph for this function:

◆ extract_families()

"dict[str, Set[str]]" audit_ingress.extract_families ( str  text)

Map each name variable built by PetscSNPrintf to every family it carries.

One buffer is normally reused for many names, so a variable maps to a set rather than a single pattern; collapsing it to the last assignment would hide every family but one.

Parameters
[in]textSource text of one translation unit.
Returns
Value returned by extract_families().

Definition at line 58 of file audit_ingress.py.

58def extract_families(text: str) -> "dict[str, Set[str]]":
59 """!
60 @brief Map each name variable built by PetscSNPrintf to every family it carries.
61 @details One buffer is normally reused for many names, so a variable maps to a
62 set rather than a single pattern; collapsing it to the last assignment
63 would hide every family but one.
64 @param[in] text Source text of one translation unit.
65 @return Value returned by `extract_families()`.
66 """
67 families: "dict[str, Set[str]]" = {}
68 for match in SNPRINTF_RE.finditer(text):
69 variable = match.group(1)
70 depth = 1
71 index = match.end()
72 while index < len(text) and depth:
73 if text[index] == "(":
74 depth += 1
75 elif text[index] == ")":
76 depth -= 1
77 index += 1
78 fragments = LITERAL_RE.findall(text[match.end():index])
79 if not fragments or not fragments[0].startswith("-"):
80 continue
81 families.setdefault(variable, set()).add(canonical_family(fragments))
82 return families
83
84
Here is the call graph for this function:
Here is the caller graph for this function:

◆ scan_option_families()

"tuple[Set[str], list[str]]" audit_ingress.scan_option_families ( Iterable[pathlib.Path]  paths)

Collect declared option families and any constructed name that lacks one.

Parameters
[in]pathsSource files to scan.
Returns
Value returned by scan_option_families().

Definition at line 85 of file audit_ingress.py.

85def scan_option_families(paths: Iterable[pathlib.Path]) -> "tuple[Set[str], list[str]]":
86 """!
87 @brief Collect declared option families and any constructed name that lacks one.
88 @param[in] paths Source files to scan.
89 @return Value returned by `scan_option_families()`.
90 """
91 families: Set[str] = set()
92 untraceable: "list[str]" = []
93 for path in paths:
94 text = path.read_text(encoding="utf-8")
95 built = extract_families(text)
96 for match in CONSTRUCTED_RE.finditer(text):
97 variable = match.group(1)
98 if variable in built:
99 families.update(built[variable])
100 else:
101 untraceable.append(f"{path.name}: '{variable}'")
102 return families, untraceable
103
104
Here is the call graph for this function:
Here is the caller graph for this function:

◆ scan_petsc_options()

Set[str] audit_ingress.scan_petsc_options ( Iterable[pathlib.Path]  paths)

Perform scan petsc options.

Parameters
[in]pathsArgument passed to scan_petsc_options().
Returns
Value returned by scan_petsc_options().

Definition at line 105 of file audit_ingress.py.

105def scan_petsc_options(paths: Iterable[pathlib.Path]) -> Set[str]:
106 """!
107 @brief Perform scan petsc options.
108 @param[in] paths Argument passed to `scan_petsc_options()`.
109 @return Value returned by `scan_petsc_options()`.
110 """
111 flags: Set[str] = set()
112 for path in paths:
113 text = path.read_text(encoding="utf-8")
114 for match in OPTION_RE.finditer(text):
115 flags.add(match.group(1))
116 return flags
117
118
Here is the caller graph for this function:

◆ load_manifest()

dict audit_ingress.load_manifest ( pathlib.Path  path)

Read and validate the option names, families, and sources in the manifest.

Parameters
[in]pathFilesystem path argument passed to load_manifest().
Returns
Value returned by load_manifest().

Definition at line 119 of file audit_ingress.py.

119def load_manifest(path: pathlib.Path) -> dict:
120 """!
121 @brief Read and validate the option names, families, and sources in the manifest.
122 @param[in] path Filesystem path argument passed to `load_manifest()`.
123 @return Value returned by `load_manifest()`.
124 """
125 data = json.loads(path.read_text(encoding="utf-8"))
126 result = {}
127 for key in ("known_petsc_options", "known_petsc_option_families"):
128 entries = data.get(key, [])
129 if not isinstance(entries, list):
130 raise ValueError(f"Manifest key '{key}' must be a list.")
131 bad = [opt for opt in entries if not isinstance(opt, str) or not opt.startswith("-")]
132 if bad:
133 raise ValueError(f"Manifest key '{key}' has invalid entries: {bad}")
134 result[key] = set(entries)
135 sources = data.get("sources")
136 if not isinstance(sources, list) or not all(isinstance(src, str) for src in sources):
137 raise ValueError("Manifest key 'sources' must be a list of paths.")
138 result["sources"] = sources
139 return result
140
141
Here is the caller graph for this function:

◆ main()

int audit_ingress.main ( )

Entry point for this script.

Returns
Value returned by main().

Definition at line 142 of file audit_ingress.py.

142def main() -> int:
143 """!
144 @brief Entry point for this script.
145 @return Value returned by `main()`.
146 """
147 parser = argparse.ArgumentParser(
148 description=(
149 "Scan PETSc option ingress in src/setup.c and src/io.c, then compare "
150 "against tests/tooling/audit_ingress_manifest.json."
151 ),
152 formatter_class=argparse.RawDescriptionHelpFormatter,
153 epilog=(
154 "Examples:\n"
155 " python3 tests/tooling/audit_ingress.py\n"
156 " python3 tests/tooling/audit_ingress.py --show-scanned\n"
157 " python3 tests/tooling/audit_ingress.py --manifest tests/tooling/audit_ingress_manifest.json\n"
158 ),
159 )
160 parser.add_argument(
161 "--manifest",
162 default="tests/tooling/audit_ingress_manifest.json",
163 help=(
164 "Manifest JSON path, relative to repository root unless absolute "
165 "(default: tests/tooling/audit_ingress_manifest.json)."
166 ),
167 )
168 parser.add_argument(
169 "--show-scanned",
170 action="store_true",
171 help="Print discovered PETSc options before drift comparison.",
172 )
173 args = parser.parse_args()
174
175 repo_root = pathlib.Path(__file__).resolve().parents[2]
176 manifest_path = (repo_root / args.manifest).resolve()
177
178 if not manifest_path.exists():
179 print(f"[ERROR] Manifest not found: {manifest_path}", file=sys.stderr)
180 return 2
181
182 manifest = load_manifest(manifest_path)
183 #: Scan paths come from the manifest so a new parse site cannot be added
184 #: without also being declared to the audit.
185 scan_paths = [repo_root / src for src in manifest["sources"]]
186 absent = [str(path) for path in scan_paths if not path.exists()]
187 if absent:
188 print(f"[ERROR] Manifest names sources that do not exist: {absent}", file=sys.stderr)
189 return 2
190
191 scanned = scan_petsc_options(scan_paths)
192 expected = manifest["known_petsc_options"]
193 scanned_families, untraceable = scan_option_families(scan_paths)
194 expected_families = manifest["known_petsc_option_families"]
195
196 missing_in_manifest = sorted(scanned - expected)
197 stale_in_manifest = sorted(expected - scanned)
198 missing_families = sorted(scanned_families - expected_families)
199 stale_families = sorted(expected_families - scanned_families)
200
201 if args.show_scanned:
202 print("[INFO] Scanned PETSc options:")
203 for flag in sorted(scanned):
204 print(flag)
205 print("")
206
207 print(f"[INFO] Scanned options: {len(scanned)}")
208 print(f"[INFO] Manifest options: {len(expected)}")
209 print(f"[INFO] Scanned option families: {len(scanned_families)}")
210 print(f"[INFO] Manifest option families: {len(expected_families)}")
211
212 if missing_in_manifest:
213 print("[ERROR] New PETSc ingress options missing in manifest:")
214 for flag in missing_in_manifest:
215 print(f" - {flag}")
216
217 if stale_in_manifest:
218 print("[ERROR] Manifest options no longer present in setup/io scan:")
219 for flag in stale_in_manifest:
220 print(f" - {flag}")
221
222 if missing_families:
223 print("[ERROR] Constructed option families missing in manifest:")
224 for family in missing_families:
225 print(f" - {family}")
226
227 if stale_families:
228 print("[ERROR] Manifest option families no longer present in the scan:")
229 for family in stale_families:
230 print(f" - {family}")
231
232 if untraceable:
233 print("[ERROR] Option names built from a variable this audit cannot trace to a family:")
234 for entry in untraceable:
235 print(f" - {entry}")
236 print(
237 " Build the name with PetscSNPrintf from a literal format beginning with '-', "
238 "so the audit can see it."
239 )
240
241 if missing_in_manifest or stale_in_manifest or missing_families or stale_families or untraceable:
242 print(
243 "[FAIL] Ingress drift detected. Update tests/tooling/audit_ingress_manifest.json and docs mapping.",
244 file=sys.stderr,
245 )
246 return 1
247
248 print("[OK] Ingress manifest matches the PETSc option scan of its declared sources.")
249 return 0
250
251
int main(int argc, char **argv)
Entry point for the postprocessor executable.
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ _ACCESSOR

str audit_ingress._ACCESSOR = r'PetscOptions(?:Get(?:Int|Real|Bool|String|IntArray|RealArray)|HasName)'
protected

Definition at line 30 of file audit_ingress.py.

◆ OPTION_RE

audit_ingress.OPTION_RE = re.compile(_ACCESSOR + r'\s*\‍(\s*NULL\s*,\s*NULL\s*,\s*"(-[^"]+)"')

Definition at line 32 of file audit_ingress.py.

◆ CONSTRUCTED_RE

audit_ingress.CONSTRUCTED_RE
Initial value:
1= re.compile(
2 _ACCESSOR + r'\s*\‍(\s*NULL\s*,\s*NULL\s*,\s*([A-Za-z_][A-Za-z0-9_]*)\s*,'
3)

Definition at line 35 of file audit_ingress.py.

◆ SNPRINTF_RE

audit_ingress.SNPRINTF_RE = re.compile(r'PetscSNPrintf\s*\‍(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,')

Definition at line 41 of file audit_ingress.py.

◆ LITERAL_RE

audit_ingress.LITERAL_RE = re.compile(r'"((?:[^"\\]|\\.)*)"')

Definition at line 43 of file audit_ingress.py.