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

Functions

str normalize_path (str|Path path)
 
argparse.Namespace parse_args ()
 
list[str] build_trace_ignoredirs ()
 
dict[str, dict[int, int]] collect_counts (trace.CoverageResults results)
 
tuple[int, int, float] compute_file_coverage (Path target, dict[str, dict[int, int]] counts_by_file)
 
int main ()
 

Variables

 REPO_ROOT = Path(__file__).resolve().parents[1]
 
list DEFAULT_TARGETS
 

Detailed Description

Run pytest under stdlib trace and enforce a line-coverage threshold.

Function Documentation

◆ normalize_path()

str python_coverage_gate.normalize_path ( str | Path  path)
Normalize path.

Definition at line 19 of file python_coverage_gate.py.

19def normalize_path(path: str | Path) -> str:
20 """Normalize path."""
21 return str(Path(path).resolve())
22
23
Here is the caller graph for this function:

◆ parse_args()

argparse.Namespace python_coverage_gate.parse_args ( )
Parse args.

Definition at line 24 of file python_coverage_gate.py.

24def parse_args() -> argparse.Namespace:
25 """Parse args."""
26 parser = argparse.ArgumentParser(
27 description=__doc__,
28 formatter_class=argparse.RawDescriptionHelpFormatter,
29 epilog=(
30 "Examples:\n"
31 " python3 scripts/python_coverage_gate.py\n"
32 " python3 scripts/python_coverage_gate.py --target scripts/picurv --target scripts/grid.gen\n"
33 " python3 scripts/python_coverage_gate.py --pytest-args -- -q tests/test_cli_smoke.py\n"
34 ),
35 )
36 parser.add_argument(
37 "--min-line",
38 type=float,
39 default=70.0,
40 help="Minimum required weighted line coverage percent (default: 70.0).",
41 )
42 parser.add_argument(
43 "--target",
44 action="append",
45 default=[],
46 help=(
47 "Repository-relative file to include in coverage computation "
48 "(repeatable). Defaults to core runtime scripts."
49 ),
50 )
51 parser.add_argument(
52 "--output-dir",
53 default="coverage/python",
54 help="Repository-relative output directory for coverage artifacts (default: coverage/python).",
55 )
56 parser.add_argument(
57 "--pytest-args",
58 nargs=argparse.REMAINDER,
59 default=["-q"],
60 help="Arguments passed to pytest (prefix with --pytest-args -- ...).",
61 )
62 return parser.parse_args()
63
64
Here is the caller graph for this function:

◆ build_trace_ignoredirs()

list[str] python_coverage_gate.build_trace_ignoredirs ( )
Build trace ignoredirs.

Definition at line 65 of file python_coverage_gate.py.

65def build_trace_ignoredirs() -> list[str]:
66 """Build trace ignoredirs."""
67 ignoredirs = {
68 normalize_path(sys.prefix),
69 normalize_path(sys.exec_prefix),
70 normalize_path(Path(sys.prefix) / "lib"),
71 }
72 for raw in list(sys.path):
73 if not raw:
74 continue
75 path = Path(raw)
76 if not path.exists():
77 continue
78 resolved = normalize_path(path)
79 if "/site-packages" in resolved or "/dist-packages" in resolved:
80 ignoredirs.add(resolved)
81 return sorted(ignoredirs)
82
83
Head of a generic C-style linked list.
Definition variables.h:372
Here is the call graph for this function:
Here is the caller graph for this function:

◆ collect_counts()

dict[str, dict[int, int]] python_coverage_gate.collect_counts ( trace.CoverageResults  results)
Collect counts.

Definition at line 84 of file python_coverage_gate.py.

84def collect_counts(results: trace.CoverageResults) -> dict[str, dict[int, int]]:
85 """Collect counts."""
86 counts_by_file: dict[str, dict[int, int]] = {}
87 for (filename, lineno), count in results.counts.items():
88 file_key = normalize_path(filename)
89 counts_by_file.setdefault(file_key, {})[lineno] = count
90 return counts_by_file
91
92
Here is the call graph for this function:
Here is the caller graph for this function:

◆ compute_file_coverage()

tuple[int, int, float] python_coverage_gate.compute_file_coverage ( Path  target,
dict[str, dict[int, int]]  counts_by_file 
)
Compute file coverage.

Definition at line 93 of file python_coverage_gate.py.

93def compute_file_coverage(target: Path, counts_by_file: dict[str, dict[int, int]]) -> tuple[int, int, float]:
94 """Compute file coverage."""
95 finder = getattr(trace, "find_executable_linenos", None)
96 if finder is None:
97 finder = trace._find_executable_linenos # type: ignore[attr-defined]
98 executable = finder(str(target))
99 executable_lines = set(executable.keys())
100 total = len(executable_lines)
101 if total == 0:
102 return 0, 0, 100.0
103
104 observed = counts_by_file.get(normalize_path(target), {})
105 covered = sum(1 for line in executable_lines if observed.get(line, 0) > 0)
106 percent = (100.0 * covered) / total
107 return covered, total, percent
108
109
Here is the call graph for this function:
Here is the caller graph for this function:

◆ main()

int python_coverage_gate.main ( )
Entry point for this script.

Definition at line 110 of file python_coverage_gate.py.

110def main() -> int:
111 """Entry point for this script."""
112 args = parse_args()
113 output_dir = (REPO_ROOT / args.output_dir).resolve()
114 output_dir.mkdir(parents=True, exist_ok=True)
115
116 targets_raw = args.target if args.target else DEFAULT_TARGETS
117 targets = [Path(REPO_ROOT / rel).resolve() for rel in targets_raw]
118 for target in targets:
119 if not target.exists():
120 raise SystemExit(f"[coverage-python] target not found: {target}")
121
122 pytest_args = list(args.pytest_args)
123 if pytest_args and pytest_args[0] == "--":
124 pytest_args = pytest_args[1:]
125 if not pytest_args:
126 pytest_args = ["-q"]
127
128 ignoredirs = build_trace_ignoredirs()
129
130 import pytest
131
132 tracer = trace.Trace(count=True, trace=False, ignoredirs=ignoredirs)
133 exit_code = tracer.runfunc(pytest.main, pytest_args)
134 results = tracer.results()
135
136 counts_by_file = collect_counts(results)
137
138 print("[coverage-python] per-file line coverage")
139 print("[coverage-python] -----------------------------------------------")
140
141 total_cov = 0
142 total_exec = 0
143 for target in targets:
144 covered, executable, percent = compute_file_coverage(target, counts_by_file)
145 total_cov += covered
146 total_exec += executable
147 rel = target.relative_to(REPO_ROOT)
148 print(f"[coverage-python] {rel}: {covered}/{executable} ({percent:.2f}%)")
149
150 overall = 100.0 if total_exec == 0 else (100.0 * total_cov) / total_exec
151 print("[coverage-python] -----------------------------------------------")
152 print(f"[coverage-python] weighted total: {total_cov}/{total_exec} ({overall:.2f}%)")
153 print(f"[coverage-python] minimum required: {args.min_line:.2f}%")
154
155 summary_path = output_dir / "summary.txt"
156 summary_path.write_text(
157 "\n".join(
158 [
159 f"weighted_total={overall:.4f}",
160 f"covered_lines={total_cov}",
161 f"executable_lines={total_exec}",
162 f"minimum_required={args.min_line:.4f}",
163 ]
164 )
165 + "\n",
166 encoding="utf-8",
167 )
168
169 if int(exit_code) != 0:
170 print(f"[coverage-python] pytest failed with exit code {exit_code}.", file=sys.stderr)
171 return int(exit_code)
172 if overall < args.min_line:
173 print(
174 f"[coverage-python] FAIL: coverage {overall:.2f}% is below required {args.min_line:.2f}%.",
175 file=sys.stderr,
176 )
177 return 2
178 return 0
179
180
int main(int argc, char **argv)
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ REPO_ROOT

python_coverage_gate.REPO_ROOT = Path(__file__).resolve().parents[1]

Definition at line 13 of file python_coverage_gate.py.

◆ DEFAULT_TARGETS

list python_coverage_gate.DEFAULT_TARGETS
Initial value:
1= [
2 "scripts/picurv",
3]

Definition at line 14 of file python_coverage_gate.py.