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
|
#!/usr/bin/env python3
"""How much of slosh the test suite actually runs.
make coverage
Builds a second binary with gcc's `--coverage`, points the suite at it with
$SLOSH_BIN, and reads the counters back with gcov. Nothing is installed and
nothing else changes: the normal build is still zig, and this one lives
entirely in build/cov.
It is a number to look at, not a number to chase. A line that no test executes
is worth knowing about; a line executed by a test that asserts nothing is
worth nothing, and no coverage tool can tell the two apart.
"""
import os
import re
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
COV = os.path.join(ROOT, "build", "cov")
def gcov_one(src):
"""gcov prints a summary per file; the counters are in build/cov."""
out = subprocess.run(
["gcov", "-o", COV, "-n", os.path.join("src", src)],
capture_output=True,
text=True,
cwd=ROOT,
).stdout
# File 'src/app.c'
# Lines executed:62.34% of 1234
pct = lines = None
seen = False
for line in out.split("\n"):
if line.startswith("File '") and line.endswith("src/%s'" % src):
seen = True
elif seen and line.startswith("Lines executed:"):
m = re.match(r"Lines executed:([\d.]+)% of (\d+)", line)
if m:
pct, lines = float(m.group(1)), int(m.group(2))
break
return pct, lines
def main():
if not os.path.isdir(COV):
print("no coverage build: run `make coverage`", file=sys.stderr)
return 1
rows = []
for src in sorted(os.listdir(os.path.join(ROOT, "src"))):
if not src.endswith(".c"):
continue
pct, lines = gcov_one(src)
if pct is None:
continue
rows.append((src, pct, lines))
if not rows:
print(
"no counters: did the suite run against build/cov/slosh?", file=sys.stderr
)
return 1
total = sum(l for _, _, l in rows)
hit = sum(round(p * l / 100) for _, p, l in rows)
print(f"{'file':<16} {'lines':>7} {'covered':>8}")
print("-" * 34)
for src, pct, lines in sorted(rows, key=lambda r: -r[2]):
bar = "#" * int(pct / 5)
print(f"{src:<16} {lines:>7} {pct:>7.1f}% {bar}")
print("-" * 34)
print(f"{'total':<16} {total:>7} {100.0 * hit / total:>7.1f}%")
# One number here is a lie for a mechanical reason, and an unexplained low
# number is worse than none: it invites someone to "fix" tested code.
print()
print("server.c reads low because the session server is a daemonised")
print("child that ends with _exit(), which skips the flush gcov relies")
print("on. Its counters are lost, not its tests -- test_session.py and")
print("the live suite drive that path.")
return 0
sys.exit(main())
|