Files
stack/dev/scripts/coverage_badge.py
kert 85ce5e719d
Some checks failed
CI / skinny-install (aco) (push) Successful in 45s
CI / skinny-install (api) (push) Successful in 29s
CI / skinny-install (bcda) (push) Successful in 25s
CI / skinny-install (bib) (push) Successful in 23s
CI / skinny-install (bls) (push) Successful in 20s
CI / skinny-install (ccw) (push) Successful in 36s
CI / skinny-install (cli) (push) Successful in 27s
CI / skinny-install (cms) (push) Successful in 24s
CI / skinny-install (conf) (push) Successful in 27s
CI / skinny-install (pfs) (push) Successful in 25s
CI / skinny-install (rex) (push) Successful in 25s
CI / lint-test (push) Successful in 6m2s
Infra CI / notebooks (push) Successful in 7s
Infra CI / zotero (push) Failing after 6s
Infra CI / docs (push) Successful in 33s
Infra CI / api (push) Successful in 6s
Infra CI / mc (push) Successful in 7s
Deploy / build-scan-report (push) Has been cancelled
chore: clean sweep — lint, format, stale refs, generated artifacts
- Fix all 72 ruff lint errors (unused imports, unused variables, E402)
- Format all 14 unformatted dev/scripts files
- Move generated artifacts to assets/ (dag.html, pfs.html)
- Remove duplicate root coverage.svg (already in assets/icons/)
- Update .dockerignore for infra/ tree layout
- Update .gitignore: add .env.bak, mirrors/, htmlcov/
- Fix stale path refs in coverage_badge.py, woodpecker backend,
  test_network_isolation.sh, docs custom.css
- Add .gitkeep to empty dirs (infra/polaris, cloud/*/terraform)
- Delete 12 stale local branches, 10 stale remote branches
2026-03-24 17:33:55 -04:00

150 lines
4.4 KiB
Python

"""Generate a coverage badge SVG and optionally post status to Gitea.
Usage:
# Generate badge SVG to stdout from piped pytest-cov output:
uv run pytest --cov=src ... | uv run python dev/scripts/coverage_badge.py
# Post coverage as a Gitea commit status (reads GITEA_TOKEN, GITEA_URL,
# CI_COMMIT_SHA from environment):
uv run python dev/scripts/coverage_badge.py --post pytest.out
# Update assets/icons/coverage.svg in the repo via Gitea API:
uv run python dev/scripts/coverage_badge.py --upload coverage.svg
"""
from __future__ import annotations
import base64
import json
import os
import re
import sys
import urllib.error
import urllib.request
TEMPLATE = """\
<svg xmlns="http://www.w3.org/2000/svg" width="116" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="116" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h65v20H0z"/>
<path fill="{color}" d="M65 0h51v20H65z"/>
<path fill="url(#b)" d="M0 0h116v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
<text x="32.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="32.5" y="14">coverage</text>
<text x="90" y="15" fill="#010101" fill-opacity=".3">{pct}%</text>
<text x="90" y="14">{pct}%</text>
</g>
</svg>"""
FILE_PATH = "assets/icons/coverage.svg"
def color_for(pct: int) -> str:
if pct >= 95:
return "#4c1"
if pct >= 80:
return "#a3c51c"
if pct >= 60:
return "#dfb317"
return "#e05d44"
def extract_pct(text: str) -> int:
m = re.search(r"^TOTAL\s+\d+\s+\d+\s+(\d+)%", text, re.MULTILINE)
if not m:
print("ERROR: could not find coverage percentage in input", file=sys.stderr)
sys.exit(1)
return int(m.group(1))
def _gitea_env() -> tuple[str, str]:
return os.environ["GITEA_TOKEN"], os.environ["GITEA_URL"]
def post_status(pct: int) -> None:
token, url = _gitea_env()
sha = os.environ["CI_COMMIT_SHA"]
endpoint = f"{url}/api/v1/repos/homelab/stack/statuses/{sha}"
body = json.dumps(
{
"state": "success",
"description": f"{pct}% coverage",
"context": "coverage",
}
).encode()
req = urllib.request.Request(
endpoint,
data=body,
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req) as resp:
print(f"Posted coverage status: {pct}% (HTTP {resp.status})")
def upload_badge(svg_path: str) -> None:
"""Update assets/icons/coverage.svg in the repo via Gitea contents API."""
token, url = _gitea_env()
endpoint = f"{url}/api/v1/repos/homelab/stack/contents/{FILE_PATH}"
svg_data = open(svg_path, "rb").read()
content_b64 = base64.b64encode(svg_data).decode()
# Get current file SHA (needed for update)
sha = None
try:
req = urllib.request.Request(
endpoint,
headers={"Authorization": f"token {token}"},
)
with urllib.request.urlopen(req) as resp:
sha = json.loads(resp.read())["sha"]
except urllib.error.HTTPError:
pass
body = {"content": content_b64, "message": "update coverage badge [skip ci]"}
if sha:
body["sha"] = sha
method = "PUT" if sha else "POST"
req = urllib.request.Request(
endpoint,
data=json.dumps(body).encode(),
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
},
method=method,
)
with urllib.request.urlopen(req) as resp:
print(f"Updated {FILE_PATH} in repo (HTTP {resp.status})")
def main() -> None:
if "--post" in sys.argv:
path = sys.argv[sys.argv.index("--post") + 1]
text = open(path).read()
pct = extract_pct(text)
post_status(pct)
elif "--upload" in sys.argv:
svg_path = sys.argv[sys.argv.index("--upload") + 1]
upload_badge(svg_path)
else:
text = sys.stdin.read()
pct = extract_pct(text)
print(TEMPLATE.format(pct=pct, color=color_for(pct)))
if __name__ == "__main__":
main()