26 parser = argparse.ArgumentParser(
28 formatter_class=argparse.RawDescriptionHelpFormatter,
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"
40 help=
"Minimum required weighted line coverage percent (default: 70.0).",
47 "Repository-relative file to include in coverage computation "
48 "(repeatable). Defaults to core runtime scripts."
53 default=
"coverage/python",
54 help=
"Repository-relative output directory for coverage artifacts (default: coverage/python).",
58 nargs=argparse.REMAINDER,
60 help=
"Arguments passed to pytest (prefix with --pytest-args -- ...).",
62 return parser.parse_args()
84def collect_counts(results: trace.CoverageResults) -> dict[str, dict[int, int]]:
86 counts_by_file: dict[str, dict[int, int]] = {}
87 for (filename, lineno), count
in results.counts.items():
89 counts_by_file.setdefault(file_key, {})[lineno] = count
94 """Compute file coverage."""
95 finder = getattr(trace,
"find_executable_linenos",
None)
97 finder = trace._find_executable_linenos
98 executable = finder(str(target))
99 executable_lines = set(executable.keys())
100 total = len(executable_lines)
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
111 """Entry point for this script."""
113 output_dir = (REPO_ROOT / args.output_dir).resolve()
114 output_dir.mkdir(parents=
True, exist_ok=
True)
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}")
122 pytest_args =
list(args.pytest_args)
123 if pytest_args
and pytest_args[0] ==
"--":
124 pytest_args = pytest_args[1:]
132 tracer = trace.Trace(count=
True, trace=
False, ignoredirs=ignoredirs)
133 exit_code = tracer.runfunc(pytest.main, pytest_args)
134 results = tracer.results()
138 print(
"[coverage-python] per-file line coverage")
139 print(
"[coverage-python] -----------------------------------------------")
143 for target
in targets:
146 total_exec += executable
147 rel = target.relative_to(REPO_ROOT)
148 print(f
"[coverage-python] {rel}: {covered}/{executable} ({percent:.2f}%)")
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}%")
155 summary_path = output_dir /
"summary.txt"
156 summary_path.write_text(
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}",
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:
174 f
"[coverage-python] FAIL: coverage {overall:.2f}% is below required {args.min_line:.2f}%.",