Skip to content

Commit 4abe253

Browse files
authored
feat: add statement and branch coverage columns to index.html report (#2085)
* feat: add Statement and Branch coverage columns to index.html report * chore: update HTML gold files after layout changes * refactor: rename statements_ratio and branches_ratio to ratio_statements and ratio_branches * test: cover ratio_statements and ratio_branches in Numbers tests * refactor: unify percent calculation in Numbers using a shared helper * refactor: simplify pc_ properties using argument unpacking* * refactor: remove unnecessary tHead/tFoot conditionalizing in HTML index table
1 parent ddbafa9 commit 4abe253

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+613
-417
lines changed

coverage/htmlfiles/coverage_html.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,15 @@ coverage.wire_up_filter = function () {
140140
const table_body_rows = table.querySelectorAll("tbody tr");
141141
const no_rows = document.getElementById("no_rows");
142142

143+
const footer = table.tFoot.rows[0];
144+
const ratio_columns = Array.from(footer.cells).map(cell => Boolean(cell.dataset.ratio));
145+
143146
// Observe filter keyevents.
144147
const filter_handler = (event => {
145148
// Keep running total of each metric, first index contains number of shown rows
146-
const totals = new Array(table.rows[0].cells.length).fill(0);
147-
// Accumulate the percentage as fraction
148-
totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection
149+
const totals = ratio_columns.map(
150+
is_ratio => is_ratio ? {"numer": 0, "denom": 0} : 0
151+
);
149152

150153
var text = document.getElementById("filter").value;
151154
// Store filter value
@@ -194,8 +197,8 @@ coverage.wire_up_filter = function () {
194197
if (cell.classList.contains("name")) {
195198
continue;
196199
}
197-
if (column === totals.length - 1) {
198-
// Last column contains percentage
200+
if (ratio_columns[column] && cell.dataset.ratio) {
201+
// Column stores a ratio
199202
const [numer, denom] = cell.dataset.ratio.split(" ");
200203
totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection
201204
totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection
@@ -218,7 +221,6 @@ coverage.wire_up_filter = function () {
218221
no_rows.style.display = null;
219222
table.style.display = null;
220223

221-
const footer = table.tFoot.rows[0];
222224
// Calculate new dynamic sum values based on visible rows.
223225
for (let column = 0; column < totals.length; column++) {
224226
// Get footer cell element.
@@ -228,7 +230,7 @@ coverage.wire_up_filter = function () {
228230
}
229231

230232
// Set value into dynamic footer cell element.
231-
if (column === totals.length - 1) {
233+
if (ratio_columns[column]) {
232234
// Percentage column uses the numerator and denominator,
233235
// and adapts to the number of decimal places.
234236
const match = /\.([0-9]+)/.exec(cell.textContent);

coverage/htmlfiles/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,30 @@ <h2>
8181
<table class="index" data-sortable>
8282
<thead>
8383
{# The title="" attr doesn't work in Safari. #}
84+
{% if has_arcs %}
85+
<tr class="tablehead grouphead">
86+
<th>&nbsp;</th>
87+
{% if region_noun %}
88+
<th>&nbsp;</th>
89+
{% endif %}
90+
<th colspan="4" class="group-label">Statements</th>
91+
<th colspan="3" class="group-label">Branches</th>
92+
<th colspan="1" class="group-label">Overall</th>
93+
</tr>
94+
{% endif %}
8495
<tr class="tablehead" title="Click to sort">
8596
<th id="file" class="name left" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
8697
{% if region_noun %}
8798
<th id="region" class="name left" aria-sort="none" data-default-sort-order="ascending" data-shortcut="n">{{ region_noun }}<span class="arrows"></span></th>
8899
{% endif %}
100+
{% if has_arcs %}
101+
<th id="statements_coverage" aria-sort="none" data-default-sort-order="descending">coverage<span class="arrows"></span></th>
102+
{% endif %}
89103
<th id="statements" aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements<span class="arrows"></span></th>
90104
<th id="missing" aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing<span class="arrows"></span></th>
91105
<th id="excluded" aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded<span class="arrows"></span></th>
92106
{% if has_arcs %}
107+
<th id="branches_coverage" aria-sort="none" data-default-sort-order="descending">coverage<span class="arrows"></span></th>
93108
<th id="branches" aria-sort="none" data-default-sort-order="descending" data-shortcut="b">branches<span class="arrows"></span></th>
94109
<th id="partial" aria-sort="none" data-default-sort-order="descending" data-shortcut="p">partial<span class="arrows"></span></th>
95110
{% endif %}
@@ -103,10 +118,14 @@ <h2>
103118
{% if region_noun %}
104119
<td class="name left"><a href="{{region.url}}">{{region.description}}</a></td>
105120
{% endif %}
121+
{% if has_arcs %}
122+
<td data-ratio="{{region.nums.ratio_statements|pair}}">{{region.nums.pc_statements_str}}%</td>
123+
{% endif %}
106124
<td>{{region.nums.n_statements}}</td>
107125
<td>{{region.nums.n_missing}}</td>
108126
<td>{{region.nums.n_excluded}}</td>
109127
{% if has_arcs %}
128+
<td data-ratio="{{region.nums.ratio_branches|pair}}">{{region.nums.pc_branches_str}}%</td>
110129
<td>{{region.nums.n_branches}}</td>
111130
<td>{{region.nums.n_partial_branches}}</td>
112131
{% endif %}
@@ -120,10 +139,14 @@ <h2>
120139
{% if region_noun %}
121140
<td class="name left">&nbsp;</td>
122141
{% endif %}
142+
{% if has_arcs %}
143+
<td data-ratio="{{totals.ratio_statements|pair}}">{{totals.pc_statements_str}}%</td>
144+
{% endif %}
123145
<td>{{totals.n_statements}}</td>
124146
<td>{{totals.n_missing}}</td>
125147
<td>{{totals.n_excluded}}</td>
126148
{% if has_arcs %}
149+
<td data-ratio="{{totals.ratio_branches|pair}}">{{totals.pc_branches_str}}%</td>
127150
<td>{{totals.n_branches}}</td>
128151
<td>{{totals.n_partial_branches}}</td>
129152
{% endif %}

coverage/htmlfiles/style.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,10 @@ kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em
352352

353353
#index th[aria-sort="descending"] .arrows::after { content: " ▼"; }
354354

355+
#index tr.grouphead th { cursor: default; font-style: normal; text-align: left; }
356+
357+
#index tr.grouphead .group-label { font-weight: bold; }
358+
355359
#index td.name { font-size: 1.15em; }
356360

357361
#index td.name a { text-decoration: none; color: inherit; }

coverage/htmlfiles/style.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,16 @@ $border-indicator-width: .2em;
773773
content: "";
774774
}
775775
}
776+
tr.grouphead {
777+
th {
778+
cursor: default;
779+
font-style: normal;
780+
text-align: left;
781+
}
782+
.group-label {
783+
font-weight: bold;
784+
}
785+
}
776786
td.name {
777787
font-size: 1.15em;
778788
a {

coverage/results.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,36 @@ def n_executed_branches(self) -> int:
323323
"""Returns the number of executed branches."""
324324
return self.n_branches - self.n_missing_branches
325325

326+
@property
327+
def ratio_statements(self) -> tuple[int, int]:
328+
"""Return numerator/denominator for statement coverage."""
329+
return self.n_executed, self.n_statements
330+
331+
@property
332+
def ratio_branches(self) -> tuple[int, int]:
333+
"""Return numerator/denominator for branch coverage."""
334+
return self.n_executed_branches, self.n_branches
335+
336+
def _percent(self, numerator: int, denominator: int) -> float:
337+
"""Helper for pc_* properties."""
338+
if denominator > 0:
339+
return (100.0 * numerator) / denominator
340+
return 100.0
341+
326342
@property
327343
def pc_covered(self) -> float:
328344
"""Returns a single percentage value for coverage."""
329-
if self.n_statements > 0:
330-
numerator, denominator = self.ratio_covered
331-
pc_cov = (100.0 * numerator) / denominator
332-
else:
333-
pc_cov = 100.0
334-
return pc_cov
345+
return self._percent(*self.ratio_covered)
346+
347+
@property
348+
def pc_statements(self) -> float:
349+
"""Returns the percentage covered for statements."""
350+
return self._percent(*self.ratio_statements)
351+
352+
@property
353+
def pc_branches(self) -> float:
354+
"""Returns the percentage covered for branches."""
355+
return self._percent(*self.ratio_branches)
335356

336357
@property
337358
def pc_covered_str(self) -> str:
@@ -344,6 +365,16 @@ def pc_covered_str(self) -> str:
344365
"""
345366
return display_covered(self.pc_covered, self.precision)
346367

368+
@property
369+
def pc_statements_str(self) -> str:
370+
"""Returns the statement percent covered without a percent sign."""
371+
return display_covered(self.pc_statements, self.precision)
372+
373+
@property
374+
def pc_branches_str(self) -> str:
375+
"""Returns the branch percent covered without a percent sign."""
376+
return display_covered(self.pc_branches, self.precision)
377+
347378
@property
348379
def ratio_covered(self) -> tuple[int, int]:
349380
"""Return a numerator and denominator for the coverage ratio."""

tests/gold/html/a/a_py.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
55
<title>Coverage for a.py: 67%</title>
66
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
7-
<link rel="stylesheet" href="style_cb_8e611ae1.css" type="text/css">
8-
<script src="coverage_html_cb_6fb7b396.js" defer></script>
7+
<link rel="stylesheet" href="style_cb_baf449ae.css" type="text/css">
8+
<script src="coverage_html_cb_7aca8e62.js" defer></script>
99
</head>
1010
<body class="pyfile">
1111
<header>
@@ -64,8 +64,8 @@ <h2>
6464
<a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
6565
<a id="nextFileLink" class="nav" href="index.html">&#xbb; next</a>
6666
&nbsp; &nbsp; &nbsp;
67-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.0a0.dev1">coverage.py v7.6.0a0.dev1</a>,
68-
created at 2024-07-10 12:20 -0400
67+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.4a0.dev1">coverage.py v7.11.4a0.dev1</a>,
68+
created at 2025-11-15 23:12 +0900
6969
</p>
7070
<aside class="hidden">
7171
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
@@ -93,8 +93,8 @@ <h2>
9393
<a class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
9494
<a class="nav" href="index.html">&#xbb; next</a>
9595
&nbsp; &nbsp; &nbsp;
96-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.0a0.dev1">coverage.py v7.6.0a0.dev1</a>,
97-
created at 2024-07-10 12:20 -0400
96+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.4a0.dev1">coverage.py v7.11.4a0.dev1</a>,
97+
created at 2025-11-15 23:12 +0900
9898
</p>
9999
</div>
100100
</footer>

tests/gold/html/a/class_index.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
55
<title>Coverage report</title>
66
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
7-
<link rel="stylesheet" href="style_cb_8e611ae1.css" type="text/css">
8-
<script src="coverage_html_cb_6fb7b396.js" defer></script>
7+
<link rel="stylesheet" href="style_cb_15de8441.css" type="text/css">
8+
<script src="coverage_html_cb_d1619a95.js" defer></script>
99
</head>
1010
<body class="indexfile">
1111
<header>
@@ -54,8 +54,8 @@ <h2>
5454
<a class="button current">Classes</a>
5555
</h2>
5656
<p class="text">
57-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.0a0.dev1">coverage.py v7.6.0a0.dev1</a>,
58-
created at 2024-07-10 12:20 -0400
57+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.3a0.dev1">coverage.py v7.11.3a0.dev1</a>,
58+
created at 2025-11-10 15:34 +0900
5959
</p>
6060
</div>
6161
</header>
@@ -99,8 +99,8 @@ <h2>
9999
<footer>
100100
<div class="content">
101101
<p>
102-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.0a0.dev1">coverage.py v7.6.0a0.dev1</a>,
103-
created at 2024-07-10 12:20 -0400
102+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.3a0.dev1">coverage.py v7.11.3a0.dev1</a>,
103+
created at 2025-11-10 15:34 +0900
104104
</p>
105105
</div>
106106
<aside class="hidden">

tests/gold/html/a/function_index.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
55
<title>Coverage report</title>
66
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
7-
<link rel="stylesheet" href="style_cb_8e611ae1.css" type="text/css">
8-
<script src="coverage_html_cb_6fb7b396.js" defer></script>
7+
<link rel="stylesheet" href="style_cb_15de8441.css" type="text/css">
8+
<script src="coverage_html_cb_d1619a95.js" defer></script>
99
</head>
1010
<body class="indexfile">
1111
<header>
@@ -54,8 +54,8 @@ <h2>
5454
<a class="button" href="class_index.html">Classes</a>
5555
</h2>
5656
<p class="text">
57-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.0a0.dev1">coverage.py v7.6.0a0.dev1</a>,
58-
created at 2024-07-10 12:20 -0400
57+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.3a0.dev1">coverage.py v7.11.3a0.dev1</a>,
58+
created at 2025-11-10 15:34 +0900
5959
</p>
6060
</div>
6161
</header>
@@ -99,8 +99,8 @@ <h2>
9999
<footer>
100100
<div class="content">
101101
<p>
102-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.0a0.dev1">coverage.py v7.6.0a0.dev1</a>,
103-
created at 2024-07-10 12:20 -0400
102+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.3a0.dev1">coverage.py v7.11.3a0.dev1</a>,
103+
created at 2025-11-10 15:34 +0900
104104
</p>
105105
</div>
106106
<aside class="hidden">

tests/gold/html/a/index.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
55
<title>Coverage report</title>
66
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
7-
<link rel="stylesheet" href="style_cb_8e611ae1.css" type="text/css">
8-
<script src="coverage_html_cb_6fb7b396.js" defer></script>
7+
<link rel="stylesheet" href="style_cb_15de8441.css" type="text/css">
8+
<script src="coverage_html_cb_d1619a95.js" defer></script>
99
</head>
1010
<body class="indexfile">
1111
<header>
@@ -53,8 +53,8 @@ <h2>
5353
<a class="button" href="class_index.html">Classes</a>
5454
</h2>
5555
<p class="text">
56-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.0a0.dev1">coverage.py v7.6.0a0.dev1</a>,
57-
created at 2024-07-10 12:20 -0400
56+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.3a0.dev1">coverage.py v7.11.3a0.dev1</a>,
57+
created at 2025-11-10 15:34 +0900
5858
</p>
5959
</div>
6060
</header>
@@ -95,8 +95,8 @@ <h2>
9595
<footer>
9696
<div class="content">
9797
<p>
98-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.0a0.dev1">coverage.py v7.6.0a0.dev1</a>,
99-
created at 2024-07-10 12:20 -0400
98+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.3a0.dev1">coverage.py v7.11.3a0.dev1</a>,
99+
created at 2025-11-10 15:34 +0900
100100
</p>
101101
</div>
102102
<aside class="hidden">

tests/gold/html/b_branch/b_py.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
55
<title>Coverage for b.py: 70%</title>
66
<link rel="icon" sizes="32x32" href="favicon_32_cb_58284776.png">
7-
<link rel="stylesheet" href="style_cb_8e611ae1.css" type="text/css">
8-
<script src="coverage_html_cb_6fb7b396.js" defer></script>
7+
<link rel="stylesheet" href="style_cb_baf449ae.css" type="text/css">
8+
<script src="coverage_html_cb_7aca8e62.js" defer></script>
99
</head>
1010
<body class="pyfile">
1111
<header>
@@ -66,8 +66,8 @@ <h2>
6666
<a id="indexLink" class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
6767
<a id="nextFileLink" class="nav" href="index.html">&#xbb; next</a>
6868
&nbsp; &nbsp; &nbsp;
69-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.8a0.dev1">coverage.py v7.6.8a0.dev1</a>,
70-
created at 2024-11-23 15:15 -0500
69+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.4a0.dev1">coverage.py v7.11.4a0.dev1</a>,
70+
created at 2025-11-15 23:12 +0900
7171
</p>
7272
<aside class="hidden">
7373
<button type="button" class="button_next_chunk" data-shortcut="j"></button>
@@ -117,8 +117,8 @@ <h2>
117117
<a class="nav" href="index.html">&Hat; index</a> &nbsp; &nbsp;
118118
<a class="nav" href="index.html">&#xbb; next</a>
119119
&nbsp; &nbsp; &nbsp;
120-
<a class="nav" href="https://coverage.readthedocs.io/en/7.6.8a0.dev1">coverage.py v7.6.8a0.dev1</a>,
121-
created at 2024-11-23 15:15 -0500
120+
<a class="nav" href="https://coverage.readthedocs.io/en/7.11.4a0.dev1">coverage.py v7.11.4a0.dev1</a>,
121+
created at 2025-11-15 23:12 +0900
122122
</p>
123123
</div>
124124
</footer>

0 commit comments

Comments
 (0)