Skip to content

Commit 841ffee

Browse files
committed
tests: Add script to annotate llvm-cov report html
1 parent 31bf211 commit 841ffee

5 files changed

Lines changed: 177 additions & 0 deletions

File tree

coverage-annotator/.gitignore

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# ─── Python ────────────────────────────────────────────────────────────────
2+
__pycache__/
3+
*.py[cod]
4+
*.pyo
5+
*.pyd
6+
*.pyw
7+
*.so
8+
*.egg
9+
*.egg-info/
10+
.eggs/
11+
*.manifest
12+
*.spec
13+
*.log
14+
15+
# Byte-compiled / optimized / DLL files
16+
__pycache__/
17+
*.py[cod]
18+
*$py.class
19+
20+
# ─── uv / Packaging ───────────────────────────────────────────────────────
21+
.uv/
22+
uv.lock
23+
dist/
24+
build/
25+
*.whl
26+
*.tar.gz
27+
28+
# ─── Virtual Environments ─────────────────────────────────────────────────
29+
.venv/
30+
venv/
31+
env/
32+
ENV/
33+
34+
# ─── Coverage / Testing ──────────────────────────────────────────────────
35+
.coverage
36+
coverage.xml
37+
htmlcov/
38+
.pytest_cache/
39+
.cache/
40+
nosetests.xml
41+
test-results.xml
42+
43+
# ─── Editor / IDE files ──────────────────────────────────────────────────
44+
.vscode/
45+
.idea/
46+
*.swp
47+
*.swo
48+
*.bak
49+
*.orig
50+
51+
# ─── macOS / Linux / Windows OS junk ─────────────────────────────────────
52+
.DS_Store
53+
Thumbs.db
54+
ehthumbs.db
55+
desktop.ini
56+
Icon?
57+
*.tmp
58+
59+
# ─── Project-specific extras ─────────────────────────────────────────────
60+
# Ignore generated annotated coverage output
61+
coverage_with_annotations.html
62+
*.html~
63+
64+
# Ignore local data or configs
65+
local_settings.json
66+
*.env
67+
.env.*

coverage-annotator/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Utility tool to annotate output report from `llvm-cov`
2+
3+
Will annotate output report from `llvm-cov` (`index.html`), using comments included in a JSON file.
4+
5+
## Usage
6+
7+
```bash
8+
uv run add-annotations coverage/html/index.html annotations.json coverage_with_annotations.html
9+
```
10+
11+
Format of `annotations.json`, file paths must match exactly those in the report table:
12+
13+
```json
14+
{
15+
"include/evmmax/evmmax.hpp": "something something"
16+
}
17+
```

coverage-annotator/pyproject.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[project]
2+
name = "coverage-annotator"
3+
version = "0.1.0"
4+
description = "Add annotation column to HTML coverage reports using a JSON mapping"
5+
authors = [{ name = "Your Name" }]
6+
license = "MIT"
7+
readme = "README.md"
8+
requires-python = ">=3.9"
9+
10+
dependencies = [
11+
"beautifulsoup4>=4.12",
12+
]
13+
14+
[project.scripts]
15+
add-annotations = "coverage_annotator.add_annotations_to_coverage_html:main"
16+
17+
[tool.uv]
18+
package = true

coverage-annotator/src/coverage_annotator/__init__.py

Whitespace-only changes.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# evmone: Fast Ethereum Virtual Machine implementation
2+
# Copyright 2025 The evmone Authors.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
import json
6+
from bs4 import BeautifulSoup
7+
import sys
8+
9+
def add_annotations_to_coverage_html(html_path, json_path, output_path):
10+
# Load annotations from JSON file
11+
with open(json_path, 'r', encoding='utf-8') as jf:
12+
annotations = json.load(jf)
13+
14+
# Parse HTML
15+
with open(html_path, 'r', encoding='utf-8') as hf:
16+
soup = BeautifulSoup(hf, 'html.parser')
17+
18+
# Find the coverage file table
19+
# Commonly, the file list is inside a <table class="index"> or similar
20+
table = soup.find('table')
21+
if not table:
22+
raise RuntimeError("Could not find a table in the HTML file")
23+
24+
# Add new column header
25+
header_row = table.find('tr')
26+
if header_row:
27+
new_th = soup.new_tag('th')
28+
new_th.string = 'Annotation'
29+
header_row.append(new_th)
30+
31+
# Iterate over table rows (skip header)
32+
rows = table.find_all('tr')[1:]
33+
for row in rows:
34+
cols = row.find_all(['td', 'th'])
35+
if not cols:
36+
continue
37+
38+
# The first column usually contains the filename
39+
file_link = cols[0].find('a')
40+
filename = None
41+
if file_link and file_link.text:
42+
filename = file_link.text.strip()
43+
else:
44+
filename = cols[0].text.strip()
45+
46+
annotation_list = annotations.get(filename, [])
47+
48+
# Convert list to HTML bullet list or plain text
49+
if annotation_list:
50+
annotation_html = "<ul>" + "".join(
51+
f"<li>{note}</li>" for note in annotation_list
52+
) + "</ul>"
53+
else:
54+
annotation_html = "<i>—</i>"
55+
56+
# Add new cell
57+
new_td = soup.new_tag("td")
58+
new_td["class"] = "column-entry"
59+
new_td.append(BeautifulSoup(annotation_html, "html.parser"))
60+
row.append(new_td)
61+
62+
# Save modified HTML
63+
with open(output_path, 'w', encoding='utf-8') as out:
64+
out.write(str(soup))
65+
66+
print(f"✅ Annotated HTML saved to: {output_path}")
67+
68+
69+
def main():
70+
if len(sys.argv) != 4:
71+
print("Usage: uv run add-annotations coverage/html/index.html annotations.json coverage_with_annotations.html>")
72+
sys.exit(1)
73+
74+
html_path, json_path, output_path = sys.argv[1:]
75+
add_annotations_to_coverage_html(html_path, json_path, output_path)

0 commit comments

Comments
 (0)