1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
"""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
|