PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
audit_starter_content.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Certify that shipped starter templates and reusable configuration profiles stay usable."""
3
4from __future__ import annotations
5
6import json
7import subprocess
8import sys
9import tempfile
10from pathlib import Path
11
12
13REPO_ROOT = Path(__file__).resolve().parents[2]
14CONTRACT_PATH = REPO_ROOT / "tests" / "tooling" / "starter_content_contract.json"
15PICURV = REPO_ROOT / "picurv_cli" / "picurv"
16RUNTIME_EXECUTION_EXAMPLE = "execution.example.yml"
17
18
19def run_cli(args: list[str], cwd: Path = REPO_ROOT) -> subprocess.CompletedProcess[str]:
20 """!
21 @brief Run one CLI command used to validate a shipped starter artifact.
22 @param[in] args CLI argument list.
23 @param[in] cwd Working directory for the command.
24 @return Completed CLI process.
25 """
26 return subprocess.run(
27 [sys.executable, str(PICURV), *args], cwd=cwd, text=True, capture_output=True, timeout=90, check=False
28 )
29
30
31def fail(context: str, result: subprocess.CompletedProcess[str]) -> None:
32 """!
33 @brief Raise a compact error that preserves the relevant CLI output.
34 @param[in] context Human-readable operation description.
35 @param[in] result Completed failing CLI process.
36 @return None.
37 """
38 raise RuntimeError(f"{context} failed:\n{result.stdout}\n{result.stderr}")
39
40
41def validate_bundle(bundle: dict[str, str], label: str) -> None:
42 """!
43 @brief Validate one declared case/solver/monitor/post or cluster/study composition.
44 @param[in] bundle Declared role-to-path mapping.
45 @param[in] label Human-readable bundle description.
46 @return None.
47 """
48 args = ["validate"]
49 for role, path in bundle.items():
50 args.extend([f"--{role}", str(REPO_ROOT / path)])
51 result = run_cli(args)
52 if result.returncode:
53 fail(label, result)
54
55
56def audit_template_copy(template_name: str, temporary_root: Path) -> None:
57 """!
58 @brief Initialize one declared template and verify every managed file was copied faithfully.
59 @param[in] template_name Top-level example directory name.
60 @param[in] temporary_root Temporary parent directory for initialized cases.
61 @return None.
62 """
63 source = REPO_ROOT / "examples" / template_name
64 destination = temporary_root / template_name
65 result = run_cli(["init", template_name, "--dest", str(destination)])
66 if result.returncode:
67 fail(f"picurv init {template_name}", result)
68 for source_path in source.rglob("*"):
69 if not source_path.is_file():
70 continue
71 relative = source_path.relative_to(source)
72 if relative.as_posix() == RUNTIME_EXECUTION_EXAMPLE:
73 continue
74 copied_path = destination / relative
75 if not copied_path.is_file() or copied_path.read_bytes() != source_path.read_bytes():
76 raise RuntimeError(f"picurv init {template_name} did not faithfully copy {relative}")
77 if (destination / RUNTIME_EXECUTION_EXAMPLE).exists():
78 raise RuntimeError(f"picurv init {template_name} copied site-specific {RUNTIME_EXECUTION_EXAMPLE}")
79
80
81def main() -> int:
82 """!
83 @brief Run the starter-content inventory, composition, and initializer audit.
84 @return Process status code.
85 """
86 contract = json.loads(CONTRACT_PATH.read_text(encoding="utf-8"))
87 actual_templates = sorted(path.name for path in (REPO_ROOT / "examples").iterdir() if path.is_dir())
88 if actual_templates != sorted(contract["template_directories"]):
89 raise RuntimeError(f"Template inventory differs from contract: actual={actual_templates}")
90 unknown_reference_templates = set(contract["reference_only_templates"]) - set(actual_templates)
91 if unknown_reference_templates:
92 raise RuntimeError(f"Reference-only templates are not in the template inventory: {sorted(unknown_reference_templates)}")
93
94 actual_config_assets = sorted(str(path.relative_to(REPO_ROOT)) for path in (REPO_ROOT / "config").rglob("*") if path.is_file())
95 if actual_config_assets != sorted(contract["config_assets"]):
96 raise RuntimeError("Configuration asset inventory differs from the starter-content contract.")
97
98 declared_example_yamls = set(contract["auxiliary_example_yamls"])
99 for bundle in contract["case_bundles"] + contract["study_bundles"]:
100 declared_example_yamls.update(path for path in bundle.values() if path.startswith("examples/"))
101 actual_example_yamls = {str(path.relative_to(REPO_ROOT)) for path in (REPO_ROOT / "examples").rglob("*.yml")}
102 if actual_example_yamls != declared_example_yamls:
103 raise RuntimeError("Example YAML inventory differs from the declared runnable/reference compositions.")
104
105 for bundle in contract["case_bundles"]:
106 validate_bundle(bundle, f"case bundle {bundle['case']}")
107 for bundle in contract["config_role_bundles"]:
108 validate_bundle(bundle, f"config role bundle {bundle['solver']}")
109 for bundle in contract["study_bundles"]:
110 validate_bundle(bundle, f"study bundle {bundle['study']}")
111
112 with tempfile.TemporaryDirectory(prefix="picurv-starter-content-") as temporary_directory:
113 temporary_root = Path(temporary_directory)
114 for template_name in contract["template_directories"]:
115 audit_template_copy(template_name, temporary_root)
116
117 print("Starter template, example, and configuration audit passed.")
118 return 0
119
120
121if __name__ == "__main__":
122 try:
123 raise SystemExit(main())
124 except (RuntimeError, subprocess.TimeoutExpired) as error:
125 print(f"Starter-content audit failed: {error}", file=sys.stderr)
126 raise SystemExit(1)
None audit_template_copy(str template_name, Path temporary_root)
Initialize one declared template and verify every managed file was copied faithfully.
None fail(str context, subprocess.CompletedProcess[str] result)
Raise a compact error that preserves the relevant CLI output.
subprocess.CompletedProcess[str] run_cli(list[str] args, Path cwd=REPO_ROOT)
Run one CLI command used to validate a shipped starter artifact.
int main()
Run the starter-content inventory, composition, and initializer audit.
None validate_bundle(dict[str, str] bundle, str label)
Validate one declared case/solver/monitor/post or cluster/study composition.