Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b8498bf

Browse files
committedApr 1, 2023
perf: make the context shortcodes even shorter
On the coverage.py metacov, this makes the HTML 5% smaller.
1 parent 80b4794 commit b8498bf

File tree

5 files changed

+99
-62
lines changed

5 files changed

+99
-62
lines changed
 

‎coverage/html.py

+32-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77

88
import collections
99
import datetime
10+
import functools
1011
import json
1112
import os
1213
import re
1314
import shutil
15+
import string # pylint: disable=deprecated-module
1416

1517
from dataclasses import dataclass
1618
from typing import Any, Dict, Iterable, List, Optional, Tuple, TYPE_CHECKING, cast
@@ -187,6 +189,21 @@ def __init__(self, fr: FileReporter, analysis: Analysis) -> None:
187189
self.html_filename = self.rootname + ".html"
188190

189191

192+
HTML_SAFE = string.ascii_letters + string.digits + "!#$%'()*+,-./:;=?@[]^_`{|}~"
193+
194+
@functools.lru_cache(maxsize=None)
195+
def encode_int(n: int) -> str:
196+
"""Create a short HTML-safe string from an integer, using HTML_SAFE."""
197+
if n == 0:
198+
return HTML_SAFE[0]
199+
200+
r = []
201+
while n:
202+
n, t = divmod(n, len(HTML_SAFE))
203+
r.append(HTML_SAFE[t])
204+
return "".join(r)
205+
206+
190207
class HtmlReporter:
191208
"""HTML reporting."""
192209

@@ -373,7 +390,10 @@ def write_html_file(self, ftr: FileToReport, prev_html: str, next_html: str) ->
373390
contexts = collections.Counter(c for cline in file_data.lines for c in cline.contexts)
374391
context_codes = {y: i for (i, y) in enumerate(x[0] for x in contexts.most_common())}
375392
if context_codes:
376-
contexts_json = json.dumps({v: k for (k, v) in context_codes.items()}, indent=2)
393+
contexts_json = json.dumps(
394+
{encode_int(v): k for (k, v) in context_codes.items()},
395+
indent=2,
396+
)
377397
else:
378398
contexts_json = None
379399

@@ -387,9 +407,17 @@ def write_html_file(self, ftr: FileToReport, prev_html: str, next_html: str) ->
387407
tok_html = escape(tok_text) or " "
388408
html_parts.append(f'<span class="{tok_type}">{tok_html}</span>')
389409
ldata.html = "".join(html_parts)
390-
ldata.context_str = ",".join(
391-
str(context_codes[c_context]) for c_context in ldata.context_list
392-
)
410+
if ldata.context_list:
411+
encoded_contexts = [
412+
encode_int(context_codes[c_context]) for c_context in ldata.context_list
413+
]
414+
code_width = max(len(ec) for ec in encoded_contexts)
415+
ldata.context_str = (
416+
str(code_width)
417+
+ "".join(ec.ljust(code_width) for ec in encoded_contexts)
418+
)
419+
else:
420+
ldata.context_str = ""
393421

394422
if ldata.short_annotations:
395423
# 202F is NARROW NO-BREAK SPACE.

‎coverage/htmlfiles/coverage_html.js

+17-17
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,6 @@ coverage.index_ready = function () {
212212
coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
213213

214214
coverage.pyfile_ready = function () {
215-
cboxes = document.querySelectorAll('[id^=ctxs]')
216-
cboxes.forEach(function(cbox) {
217-
cbox.addEventListener("click", coverage.showContexts)
218-
});
219-
220215
// If we're directed to a particular line number, highlight the line.
221216
var frag = location.hash;
222217
if (frag.length > 2 && frag[1] === "t") {
@@ -262,6 +257,10 @@ coverage.pyfile_ready = function () {
262257
coverage.init_scroll_markers();
263258
coverage.wire_up_sticky_header();
264259

260+
document.querySelectorAll("[id^=ctxs]").forEach(
261+
cbox => cbox.addEventListener("click", coverage.expand_contexts)
262+
);
263+
265264
// Rebuild scroll markers when the window height changes.
266265
window.addEventListener("resize", coverage.build_scroll_markers);
267266
};
@@ -600,17 +599,19 @@ coverage.wire_up_sticky_header = function () {
600599
updateHeader();
601600
};
602601

603-
coverage.showContexts = function (e) {
604-
span = e.target.nextElementSibling.nextElementSibling;
605-
span_text = span.textContent;
606-
607-
if (/^[0-9,]+$/.test(span_text)) {
608-
span.textContent = "";
609-
span_text.split(",").forEach(function(s) {
610-
ctx = contexts[s];
611-
span.appendChild(document.createTextNode(ctx));
612-
span.appendChild(document.createElement("br"));
613-
})
602+
coverage.expand_contexts = function (e) {
603+
var ctxs = e.target.parentNode.querySelector(".ctxs");
604+
605+
if (!ctxs.classList.contains("expanded")) {
606+
var ctxs_text = ctxs.textContent;
607+
var width = Number(ctxs_text[0]);
608+
ctxs.textContent = "";
609+
for (var i = 1; i < ctxs_text.length; i += width) {
610+
key = ctxs_text.substring(i, i + width).trim();
611+
ctxs.appendChild(document.createTextNode(contexts[key]));
612+
ctxs.appendChild(document.createElement("br"));
613+
}
614+
ctxs.classList.add("expanded");
614615
}
615616
};
616617

@@ -620,5 +621,4 @@ document.addEventListener("DOMContentLoaded", () => {
620621
} else {
621622
coverage.pyfile_ready();
622623
}
623-
624624
});

‎tests/gold/html/contexts/two_tests_py.html

+14-14
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
<link rel="stylesheet" href="style.css" type="text/css">
88
<script type="text/javascript">
99
contexts = {
10-
"0": "(empty)",
11-
"1": "two_tests.test_two",
12-
"2": "two_tests.test_one"
10+
"a": "(empty)",
11+
"b": "two_tests.test_two",
12+
"c": "two_tests.test_one"
1313
}
1414
</script>
1515
<script type="text/javascript" src="coverage_html.js" defer></script>
@@ -72,7 +72,7 @@ <h2>
7272
<a id="nextFileLink" class="nav" href="index.html">&#xbb; next</a>
7373
&nbsp; &nbsp; &nbsp;
7474
<a class="nav" href="https://coverage.readthedocs.io/en/7.2.3a0.dev1">coverage.py v7.2.3a0.dev1</a>,
75-
created at 2023-03-22 13:16 -0400
75+
created at 2023-04-01 08:30 -0400
7676
</p>
7777
<aside class="hidden">
7878
<button type="button" class="button_next_chunk" data-shortcut="j"/>
@@ -88,20 +88,20 @@ <h2>
8888
</header>
8989
<main id="source">
9090
<p class="run"><span class="n"><a id="t1" href="#t1">1</a></span><span class="t"><span class="key">def</span> <span class="nam">helper</span><span class="op">(</span><span class="nam">lineno</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"><label for="ctxs1" class="ctx">(empty)</label></span></p>
91-
<p class="run"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"> <span class="nam">x</span> <span class="op">=</span> <span class="num">2</span>&nbsp;</span><input type="checkbox" id="ctxs2" /><span class="r"><label for="ctxs2" class="ctx">3 ctx</label></span><span class="ctxs">0,2,1</span></p>
91+
<p class="run"><span class="n"><a id="t2" href="#t2">2</a></span><span class="t"> <span class="nam">x</span> <span class="op">=</span> <span class="num">2</span>&nbsp;</span><input type="checkbox" id="ctxs2" /><span class="r"><label for="ctxs2" class="ctx">3 ctx</label></span><span class="ctxs">1acb</span></p>
9292
<p class="pln"><span class="n"><a id="t3" href="#t3">3</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
9393
<p class="run"><span class="n"><a id="t4" href="#t4">4</a></span><span class="t"><span class="key">def</span> <span class="nam">test_one</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"><label for="ctxs4" class="ctx">(empty)</label></span></p>
94-
<p class="run"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"> <span class="nam">a</span> <span class="op">=</span> <span class="num">5</span>&nbsp;</span><input type="checkbox" id="ctxs5" /><span class="r"><label for="ctxs5" class="ctx">1 ctx</label></span><span class="ctxs">2</span></p>
95-
<p class="run"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"> <span class="nam">helper</span><span class="op">(</span><span class="num">6</span><span class="op">)</span>&nbsp;</span><input type="checkbox" id="ctxs6" /><span class="r"><label for="ctxs6" class="ctx">1 ctx</label></span><span class="ctxs">2</span></p>
94+
<p class="run"><span class="n"><a id="t5" href="#t5">5</a></span><span class="t"> <span class="nam">a</span> <span class="op">=</span> <span class="num">5</span>&nbsp;</span><input type="checkbox" id="ctxs5" /><span class="r"><label for="ctxs5" class="ctx">1 ctx</label></span><span class="ctxs">1c</span></p>
95+
<p class="run"><span class="n"><a id="t6" href="#t6">6</a></span><span class="t"> <span class="nam">helper</span><span class="op">(</span><span class="num">6</span><span class="op">)</span>&nbsp;</span><input type="checkbox" id="ctxs6" /><span class="r"><label for="ctxs6" class="ctx">1 ctx</label></span><span class="ctxs">1c</span></p>
9696
<p class="pln"><span class="n"><a id="t7" href="#t7">7</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
9797
<p class="run"><span class="n"><a id="t8" href="#t8">8</a></span><span class="t"><span class="key">def</span> <span class="nam">test_two</span><span class="op">(</span><span class="op">)</span><span class="op">:</span>&nbsp;</span><span class="r"><label for="ctxs8" class="ctx">(empty)</label></span></p>
98-
<p class="run"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"> <span class="nam">a</span> <span class="op">=</span> <span class="num">9</span>&nbsp;</span><input type="checkbox" id="ctxs9" /><span class="r"><label for="ctxs9" class="ctx">1 ctx</label></span><span class="ctxs">1</span></p>
99-
<p class="run"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"> <span class="nam">b</span> <span class="op">=</span> <span class="num">10</span>&nbsp;</span><input type="checkbox" id="ctxs10" /><span class="r"><label for="ctxs10" class="ctx">1 ctx</label></span><span class="ctxs">1</span></p>
100-
<p class="run"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"> <span class="key">if</span> <span class="nam">a</span> <span class="op">></span> <span class="num">11</span><span class="op">:</span>&nbsp;</span><input type="checkbox" id="ctxs11" /><span class="r"><label for="ctxs11" class="ctx">1 ctx</label></span><span class="ctxs">1</span></p>
98+
<p class="run"><span class="n"><a id="t9" href="#t9">9</a></span><span class="t"> <span class="nam">a</span> <span class="op">=</span> <span class="num">9</span>&nbsp;</span><input type="checkbox" id="ctxs9" /><span class="r"><label for="ctxs9" class="ctx">1 ctx</label></span><span class="ctxs">1b</span></p>
99+
<p class="run"><span class="n"><a id="t10" href="#t10">10</a></span><span class="t"> <span class="nam">b</span> <span class="op">=</span> <span class="num">10</span>&nbsp;</span><input type="checkbox" id="ctxs10" /><span class="r"><label for="ctxs10" class="ctx">1 ctx</label></span><span class="ctxs">1b</span></p>
100+
<p class="run"><span class="n"><a id="t11" href="#t11">11</a></span><span class="t"> <span class="key">if</span> <span class="nam">a</span> <span class="op">></span> <span class="num">11</span><span class="op">:</span>&nbsp;</span><input type="checkbox" id="ctxs11" /><span class="r"><label for="ctxs11" class="ctx">1 ctx</label></span><span class="ctxs">1b</span></p>
101101
<p class="mis show_mis"><span class="n"><a id="t12" href="#t12">12</a></span><span class="t"> <span class="nam">b</span> <span class="op">=</span> <span class="num">12</span>&nbsp;</span><span class="r"></span></p>
102-
<p class="run"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"> <span class="key">assert</span> <span class="nam">a</span> <span class="op">==</span> <span class="op">(</span><span class="num">13</span><span class="op">-</span><span class="num">4</span><span class="op">)</span>&nbsp;</span><input type="checkbox" id="ctxs13" /><span class="r"><label for="ctxs13" class="ctx">1 ctx</label></span><span class="ctxs">1</span></p>
103-
<p class="run"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"> <span class="key">assert</span> <span class="nam">b</span> <span class="op">==</span> <span class="op">(</span><span class="num">14</span><span class="op">-</span><span class="num">4</span><span class="op">)</span>&nbsp;</span><input type="checkbox" id="ctxs14" /><span class="r"><label for="ctxs14" class="ctx">1 ctx</label></span><span class="ctxs">1</span></p>
104-
<p class="run"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"> <span class="nam">helper</span><span class="op">(</span>&nbsp;</span><input type="checkbox" id="ctxs15" /><span class="r"><label for="ctxs15" class="ctx">1 ctx</label></span><span class="ctxs">1</span></p>
102+
<p class="run"><span class="n"><a id="t13" href="#t13">13</a></span><span class="t"> <span class="key">assert</span> <span class="nam">a</span> <span class="op">==</span> <span class="op">(</span><span class="num">13</span><span class="op">-</span><span class="num">4</span><span class="op">)</span>&nbsp;</span><input type="checkbox" id="ctxs13" /><span class="r"><label for="ctxs13" class="ctx">1 ctx</label></span><span class="ctxs">1b</span></p>
103+
<p class="run"><span class="n"><a id="t14" href="#t14">14</a></span><span class="t"> <span class="key">assert</span> <span class="nam">b</span> <span class="op">==</span> <span class="op">(</span><span class="num">14</span><span class="op">-</span><span class="num">4</span><span class="op">)</span>&nbsp;</span><input type="checkbox" id="ctxs14" /><span class="r"><label for="ctxs14" class="ctx">1 ctx</label></span><span class="ctxs">1b</span></p>
104+
<p class="run"><span class="n"><a id="t15" href="#t15">15</a></span><span class="t"> <span class="nam">helper</span><span class="op">(</span>&nbsp;</span><input type="checkbox" id="ctxs15" /><span class="r"><label for="ctxs15" class="ctx">1 ctx</label></span><span class="ctxs">1b</span></p>
105105
<p class="pln"><span class="n"><a id="t16" href="#t16">16</a></span><span class="t"> <span class="num">16</span>&nbsp;</span><span class="r"></span></p>
106106
<p class="pln"><span class="n"><a id="t17" href="#t17">17</a></span><span class="t"> <span class="op">)</span>&nbsp;</span><span class="r"></span></p>
107107
<p class="pln"><span class="n"><a id="t18" href="#t18">18</a></span><span class="t">&nbsp;</span><span class="r"></span></p>
@@ -118,7 +118,7 @@ <h2>
118118
<a id="nextFileLink" class="nav" href="index.html">&#xbb; next</a>
119119
&nbsp; &nbsp; &nbsp;
120120
<a class="nav" href="https://coverage.readthedocs.io/en/7.2.3a0.dev1">coverage.py v7.2.3a0.dev1</a>,
121-
created at 2023-03-22 13:16 -0400
121+
created at 2023-04-01 08:30 -0400
122122
</p>
123123
</div>
124124
</footer>

‎tests/gold/html/support/coverage_html.js

+27-27
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,9 @@ coverage.index_ready = function () {
212212
coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
213213

214214
coverage.pyfile_ready = function () {
215-
cboxes = document.querySelectorAll('[id^=ctxs]')
216-
cboxes.forEach(function(cbox) {
217-
cbox.addEventListener("click", coverage.showContexts)
218-
});
219-
220215
// If we're directed to a particular line number, highlight the line.
221216
var frag = location.hash;
222-
if (frag.length > 2 && frag[1] === 't') {
217+
if (frag.length > 2 && frag[1] === "t") {
223218
document.querySelector(frag).closest(".n").classList.add("highlight");
224219
coverage.set_sel(parseInt(frag.substr(2), 10));
225220
} else {
@@ -262,6 +257,10 @@ coverage.pyfile_ready = function () {
262257
coverage.init_scroll_markers();
263258
coverage.wire_up_sticky_header();
264259

260+
document.querySelectorAll("[id^=ctxs]").forEach(
261+
cbox => cbox.addEventListener("click", coverage.expand_contexts)
262+
);
263+
265264
// Rebuild scroll markers when the window height changes.
266265
window.addEventListener("resize", coverage.build_scroll_markers);
267266
};
@@ -533,14 +532,14 @@ coverage.scroll_window = function (to_pos) {
533532

534533
coverage.init_scroll_markers = function () {
535534
// Init some variables
536-
coverage.lines_len = document.querySelectorAll('#source > p').length;
535+
coverage.lines_len = document.querySelectorAll("#source > p").length;
537536

538537
// Build html
539538
coverage.build_scroll_markers();
540539
};
541540

542541
coverage.build_scroll_markers = function () {
543-
const temp_scroll_marker = document.getElementById('scroll_marker')
542+
const temp_scroll_marker = document.getElementById("scroll_marker")
544543
if (temp_scroll_marker) temp_scroll_marker.remove();
545544
// Don't build markers if the window has no scroll bar.
546545
if (document.body.scrollHeight <= window.innerHeight) {
@@ -554,8 +553,8 @@ coverage.build_scroll_markers = function () {
554553

555554
const scroll_marker = document.createElement("div");
556555
scroll_marker.id = "scroll_marker";
557-
document.getElementById('source').querySelectorAll(
558-
'p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par'
556+
document.getElementById("source").querySelectorAll(
557+
"p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par"
559558
).forEach(element => {
560559
const line_top = Math.floor(element.offsetTop * marker_scale);
561560
const line_number = parseInt(element.querySelector(".n a").id.substr(1));
@@ -582,35 +581,37 @@ coverage.build_scroll_markers = function () {
582581
};
583582

584583
coverage.wire_up_sticky_header = function () {
585-
const header = document.querySelector('header');
584+
const header = document.querySelector("header");
586585
const header_bottom = (
587-
header.querySelector('.content h2').getBoundingClientRect().top -
586+
header.querySelector(".content h2").getBoundingClientRect().top -
588587
header.getBoundingClientRect().top
589588
);
590589

591590
function updateHeader() {
592591
if (window.scrollY > header_bottom) {
593-
header.classList.add('sticky');
592+
header.classList.add("sticky");
594593
} else {
595-
header.classList.remove('sticky');
594+
header.classList.remove("sticky");
596595
}
597596
}
598597

599-
window.addEventListener('scroll', updateHeader);
598+
window.addEventListener("scroll", updateHeader);
600599
updateHeader();
601600
};
602601

603-
coverage.showContexts = function (e) {
604-
span = e.target.nextElementSibling.nextElementSibling;
605-
span_text = span.textContent;
606-
607-
if (/^[0-9,]+$/.test(span_text)) {
608-
span.textContent = "";
609-
span_text.split(",").forEach(function(s) {
610-
ctx = contexts[s];
611-
span.appendChild(document.createTextNode(ctx));
612-
span.appendChild(document.createElement("br"));
613-
})
602+
coverage.expand_contexts = function (e) {
603+
var ctxs = e.target.parentNode.querySelector(".ctxs");
604+
605+
if (!ctxs.classList.contains("expanded")) {
606+
var ctxs_text = ctxs.textContent;
607+
var width = Number(ctxs_text[0]);
608+
ctxs.textContent = "";
609+
for (var i = 1; i < ctxs_text.length; i += width) {
610+
key = ctxs_text.substring(i, i + width).trim();
611+
ctxs.appendChild(document.createTextNode(contexts[key]));
612+
ctxs.appendChild(document.createElement("br"));
613+
}
614+
ctxs.classList.add("expanded");
614615
}
615616
};
616617

@@ -620,5 +621,4 @@ document.addEventListener("DOMContentLoaded", () => {
620621
} else {
621622
coverage.pyfile_ready();
622623
}
623-
624624
});

‎tests/test_html.py

+9
Original file line numberDiff line numberDiff line change
@@ -1260,3 +1260,12 @@ def test_bad_anchor(self) -> None:
12601260
msg = "Fragment '#nothing' in htmlcov.index.html has no anchor"
12611261
with pytest.raises(AssertionError, match=msg):
12621262
self.assert_valid_hrefs()
1263+
1264+
1265+
@pytest.mark.parametrize("n, key", [
1266+
(0, "a"),
1267+
(1, "b"),
1268+
(999999999, "e9S_p"),
1269+
])
1270+
def test_encode_int(n: int, key: str) -> None:
1271+
assert coverage.html.encode_int(n) == key

0 commit comments

Comments
 (0)
Please sign in to comment.