"""Tests for the Python bundle coverage-summary kernel. The script under test lives at languages/python/claude/scripts/coverage-summary.py and is loaded by path (hyphenated CLI name, not importable as a module). The kernel is the missing-file detection and the file-weighted project number; coverage.py's own `coverage report` prints the per-file table, so this script does not reimplement that. Tests build a throwaway package tree plus a faithful coverage.py JSON report and assert the kernel's numbers against hand-computed values, with Normal / Boundary / Error coverage per function. """ import importlib.util import json from pathlib import Path import pytest # Load the hyphenated CLI script by path. _SCRIPT = Path(__file__).resolve().parents[1] / "claude" / "scripts" / "coverage-summary.py" _spec = importlib.util.spec_from_file_location("coverage_summary", _SCRIPT) cs = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(cs) def write_report(root: Path, files: dict) -> Path: """Write a coverage.py-shaped JSON report. files maps a project-relative path to (covered, total). Mirrors the real schema: files[path] = {"summary": {"covered_lines", "num_statements", ...}}. """ payload = {"meta": {"version": "7.0"}, "files": {}, "totals": {}} for rel, (cov, tot) in files.items(): payload["files"][rel] = { "executed_lines": list(range(1, cov + 1)), "summary": { "covered_lines": cov, "num_statements": tot, "percent_covered": (100.0 * cov / tot) if tot else 100.0, "missing_lines": tot - cov, "excluded_lines": 0, }, "missing_lines": list(range(cov + 1, tot + 1)), "excluded_lines": [], } p = root / "coverage.json" p.write_text(json.dumps(payload)) return p @pytest.fixture def project(tmp_path): """A package tree under src/ plus a place for the report. Returns (root, src).""" src = tmp_path / "src" (src / "sub").mkdir(parents=True) return tmp_path, src # --- parse: covered / total ------------------------------------------------ def test_parse_counts_covered_and_statements(project): root, src = project report = write_report(root, {"src/a.py": (3, 5)}) table = cs.parse_report(report) key = str((root / "src/a.py").resolve()) assert table[key] == (3, 5) def test_parse_missing_report_raises(project): root, _ = project with pytest.raises(SystemExit): cs.parse_report(root / "nope.json") def test_parse_malformed_json_raises(project): root, _ = project bad = root / "coverage.json" bad.write_text("{not json") with pytest.raises(SystemExit): cs.parse_report(bad) # --- file percentage ------------------------------------------------------- def test_file_pct_normal(): assert cs.file_pct(1, 2) == 50.0 def test_file_pct_no_statements_is_100(): assert cs.file_pct(0, 0) == 100.0 def test_file_pct_fully_covered(): assert cs.file_pct(4, 4) == 100.0 # --- missing-file detection (the kernel) ----------------------------------- def test_missing_finds_ondisk_file_absent_from_report(project): root, src = project (src / "tracked.py").write_text("x = 1\n") (src / "untested.py").write_text("y = 2\n") report = write_report(root, {"src/tracked.py": (1, 1)}) table = cs.under_dir(cs.parse_report(report), src, root) missing = cs.missing(list(table), src, root) assert missing == ["src/untested.py"] def test_missing_recurses_into_subpackages(project): root, src = project (src / "top.py").write_text("a = 1\n") (src / "sub" / "deep.py").write_text("b = 2\n") report = write_report(root, {"src/top.py": (1, 1)}) table = cs.under_dir(cs.parse_report(report), src, root) missing = cs.missing(list(table), src, root) assert "src/sub/deep.py" in missing def test_missing_empty_when_all_tracked(project): root, src = project (src / "a.py").write_text("x = 1\n") report = write_report(root, {"src/a.py": (1, 1)}) table = cs.under_dir(cs.parse_report(report), src, root) assert cs.missing(list(table), src, root) == [] # --- project number (file-weighted, missing as 0%) ------------------------- def test_project_pct_unit_weighted_with_missing_as_zero(project): root, src = project for name in ("full.py", "half.py", "untested.py"): (src / name).write_text("x = 1\n") report = write_report(root, {"src/full.py": (2, 2), "src/half.py": (1, 2)}) # full=100, half=50, untested missing=0 -> (100+50+0)/3 = 50.0 assert cs.project_pct(report, src, root) == 50.0 def test_project_pct_no_missing(project): root, src = project for name in ("full.py", "half.py"): (src / name).write_text("x = 1\n") report = write_report(root, {"src/full.py": (2, 2), "src/half.py": (1, 2)}) assert cs.project_pct(report, src, root) == 75.0 # --- end-to-end text ------------------------------------------------------- def test_summary_text_reports_missing_and_project_number(project): root, src = project (src / "a.py").write_text("x = 1\n") (src / "orphan.py").write_text("y = 2\n") report = write_report(root, {"src/a.py": (1, 2)}) text = cs.summary_text(report, src, root) assert "orphan.py" in text assert "0%" in text # a.py = 50%, orphan missing = 0% -> 25.0% assert "25.0%" in text