32 @return Value returned by `parse_args()`.
34 parser = argparse.ArgumentParser(
36 formatter_class=argparse.RawDescriptionHelpFormatter,
39 " python3 tests/tooling/python_coverage_gate.py\n"
40 " python3 tests/tooling/python_coverage_gate.py --target picurv_cli/core.py --target generators/grid.gen\n"
41 " python3 tests/tooling/python_coverage_gate.py --pytest-args -- -q tests/test_cli_smoke.py\n"
48 help=
"Minimum required weighted line coverage percent (default: 70.0).",
55 "Repository-relative file to include in coverage computation "
56 "(repeatable). Defaults to core runtime scripts."
61 default=
"coverage/python",
62 help=
"Repository-relative output directory for coverage artifacts (default: coverage/python).",
66 nargs=argparse.REMAINDER,
68 help=
"Arguments passed to pytest (prefix with --pytest-args -- ...).",
70 return parser.parse_args()
95def collect_counts(results: trace.CoverageResults) -> dict[str, dict[int, int]]:
97 @brief Collect counts.
98 @param[in] results Argument passed to `collect_counts()`.
99 @return Value returned by `collect_counts()`.
101 counts_by_file: dict[str, dict[int, int]] = {}
102 for (filename, lineno), count
in results.counts.items():
104 counts_by_file.setdefault(file_key, {})[lineno] = count
105 return counts_by_file
110 @brief Compute file coverage.
111 @param[in] target Argument passed to `compute_file_coverage()`.
112 @param[in] counts_by_file Argument passed to `compute_file_coverage()`.
113 @return Value returned by `compute_file_coverage()`.
115 finder = getattr(trace,
"find_executable_linenos",
None)
117 finder = trace._find_executable_linenos
118 executable = finder(str(target))
119 executable_lines = set(executable.keys())
120 total = len(executable_lines)
125 covered = sum(1
for line
in executable_lines
if observed.get(line, 0) > 0)
126 percent = (100.0 * covered) / total
127 return covered, total, percent
132 @brief Entry point for this script.
133 @return Value returned by `main()`.
136 output_dir = (REPO_ROOT / args.output_dir).resolve()
137 output_dir.mkdir(parents=
True, exist_ok=
True)
139 targets_raw = args.target
if args.target
else DEFAULT_TARGETS
140 targets = [Path(REPO_ROOT / rel).resolve()
for rel
in targets_raw]
141 for target
in targets:
142 if not target.exists():
143 raise SystemExit(f
"[coverage-python] target not found: {target}")
145 pytest_args =
list(args.pytest_args)
146 if pytest_args
and pytest_args[0] ==
"--":
147 pytest_args = pytest_args[1:]
155 tracer = trace.Trace(count=
True, trace=
False, ignoredirs=ignoredirs)
156 exit_code = tracer.runfunc(pytest.main, pytest_args)
157 results = tracer.results()
161 print(
"[coverage-python] per-file line coverage")
162 print(
"[coverage-python] -----------------------------------------------")
166 for target
in targets:
169 total_exec += executable
170 rel = target.relative_to(REPO_ROOT)
171 print(f
"[coverage-python] {rel}: {covered}/{executable} ({percent:.2f}%)")
173 overall = 100.0
if total_exec == 0
else (100.0 * total_cov) / total_exec
174 print(
"[coverage-python] -----------------------------------------------")
175 print(f
"[coverage-python] weighted total: {total_cov}/{total_exec} ({overall:.2f}%)")
176 print(f
"[coverage-python] minimum required: {args.min_line:.2f}%")
178 summary_path = output_dir /
"summary.txt"
179 summary_path.write_text(
182 f
"weighted_total={overall:.4f}",
183 f
"covered_lines={total_cov}",
184 f
"executable_lines={total_exec}",
185 f
"minimum_required={args.min_line:.4f}",
192 if int(exit_code) != 0:
193 print(f
"[coverage-python] pytest failed with exit code {exit_code}.", file=sys.stderr)
194 return int(exit_code)
195 if overall < args.min_line:
197 f
"[coverage-python] FAIL: coverage {overall:.2f}% is below required {args.min_line:.2f}%.",