mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:27:02 +01:00
kcov works here once invoked correctly, so shell coverage is measurable after all. shell_coverage.sh wraps it as a gate; the XML parsing lives in shell_coverage_report.py per the rule against inlining another language's logic in a shell script. Two kcov traps are documented in the wrapper because both fail silently. It must be handed the test script DIRECTLY: given `bash script.sh` it instruments the bash binary, finds no shell source, and reports 0/0 at 0.00% -- indistinguishable from "nothing is covered". And per-line detail exists only in cov.xml; coverage.json carries the percentage alone, so it cannot say which lines are missing. First measurement put dns_iptables.sh at 68/74 = 91.89%, with all six uncovered lines in error and edge branches: chain creation failing, jump insertion failing, the stale-jump de-dupe log, and the v6 trusted-resolver ACCEPT. The stub grew failure injection and jump seeding to reach them, and four new cases take it to 74/74 = 100.00%. The gate fails closed: deleting the three ensure_chain failure cases drops it to 93.24%, names lines 23, 24, 37, 40, 41, and exits 1. Not wired into pre-commit -- kcov is absent on CI, and most shell files here have no test at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
68 lines
2.4 KiB
Python
Executable File
68 lines
2.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Report line coverage for one shell file from a kcov output directory.
|
|
|
|
Reads kcov's cobertura-style ``cov.xml`` rather than its ``coverage.json``:
|
|
the JSON summary carries only a percentage, so it cannot say which lines are
|
|
missing, which is the only part that helps close a gap.
|
|
|
|
Exits 1 when coverage is below the requested minimum, so the caller can use
|
|
it as a gate.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
from defusedxml import ElementTree
|
|
|
|
|
|
def _measure(out_dir: Path, subject: str) -> tuple[int, int, list[str]]:
|
|
"""Return (covered, total, uncovered line numbers) for *subject*.
|
|
|
|
kcov writes one report per instrumented process. A harness that stages
|
|
the subject into a temp dir produces several, so the entries are merged:
|
|
a line counts as covered if any process hit it.
|
|
"""
|
|
hits: dict[str, int] = {}
|
|
for xml_path in out_dir.rglob("cov.xml"):
|
|
for cls in ElementTree.parse(xml_path).iter("class"):
|
|
filename = cls.get("filename", "")
|
|
if Path(filename).name != subject:
|
|
continue
|
|
for line in cls.iter("line"):
|
|
number = line.get("number")
|
|
if number is None:
|
|
continue
|
|
hits[number] = hits.get(number, 0) + int(line.get("hits", "0"))
|
|
|
|
uncovered = sorted((n for n, h in hits.items() if h == 0), key=int)
|
|
return len(hits) - len(uncovered), len(hits), uncovered
|
|
|
|
|
|
def main() -> None:
|
|
"""Print the coverage summary and gate on the minimum percentage."""
|
|
out_dir, subject, minimum = Path(sys.argv[1]), sys.argv[2], float(sys.argv[3])
|
|
|
|
covered, total, uncovered = _measure(out_dir, subject)
|
|
if total == 0:
|
|
sys.exit(
|
|
f"{subject}: kcov instrumented no lines. It was probably invoked as "
|
|
f"`kcov ... bash <script>`; hand it the script directly instead."
|
|
)
|
|
|
|
percent = 100.0 * covered / total
|
|
# sys.stdout.write rather than print, matching extract_shell_functions.py:
|
|
# this is the tool's result, not debug output, and ruff's T201 autofix
|
|
# silently deletes bare print() calls.
|
|
sys.stdout.write(f"{subject}: {covered}/{total} lines = {percent:.2f}%\n")
|
|
if uncovered:
|
|
sys.stdout.write(f" uncovered: {', '.join(uncovered)}\n")
|
|
|
|
if percent < minimum:
|
|
sys.exit(f"coverage {percent:.2f}% is below the required {minimum:.2f}%")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|