mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Bump Python tooling to Ruff 0.16.2 and datamodel-code-generator 0.72.3, add the py313 target in pre-commit, and refresh the engine lockfile. This also narrows broad exception handling and applies small Python cleanups across the engine and helper scripts to satisfy newer lint rules without changing runtime behavior.
169 lines
5.6 KiB
Python
169 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Aggregate per-test V8 coverage dumps from Playwright into one summary.
|
|
|
|
Playwright's test-base fixture writes raw `page.coverage.stopJSCoverage()`
|
|
output to a directory (one JSON file per test, when `PW_COVERAGE=1`).
|
|
This script walks that directory and produces:
|
|
|
|
- coverage-summary.json (vitest-shaped, so the existing
|
|
scripts/coverage-summary.py renderer can consume it without changes)
|
|
|
|
Method:
|
|
- V8 reports per-function coverage as a list of ranges; the first
|
|
range covers the whole function body, and a non-zero `count` on
|
|
that outer range means the function was entered at least once.
|
|
- We deduplicate functions across the merged dumps by (script-url,
|
|
startOffset, endOffset). This avoids double-counting a function
|
|
that ran in many tests.
|
|
- Per-file line coverage requires source maps + a `v8-to-istanbul`
|
|
style walker, which is out of scope here - we report
|
|
Lines/Statements as 0/0 (the helper renders that as "not computed",
|
|
matching how it handles vitest's known v8+SWC degradation).
|
|
|
|
Filtering:
|
|
- URLs not on the local dev server (e.g. CDN assets, blob: URLs,
|
|
chrome-extension: pages) are skipped.
|
|
- URLs containing `node_modules` or vite's HMR client are skipped so
|
|
the percentage reflects app code, not framework noise.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# URLs we deliberately don't count: vite client, hot-reload runtime,
|
|
# anything served out of node_modules, and any non-http(s) scheme.
|
|
SKIP_URL_FRAGMENTS = (
|
|
"/@vite/",
|
|
"/@react-refresh",
|
|
"/node_modules/",
|
|
"/__vite_ping",
|
|
"/__open-in-editor",
|
|
)
|
|
|
|
|
|
def _is_app_url(url: str) -> bool:
|
|
if not url:
|
|
return False
|
|
if not (url.startswith(("http://", "https://"))):
|
|
return False
|
|
return not any(frag in url for frag in SKIP_URL_FRAGMENTS)
|
|
|
|
|
|
def aggregate(dump_dir: Path) -> dict:
|
|
files = sorted(dump_dir.glob("*.json"))
|
|
if not files:
|
|
return {
|
|
"tests": 0,
|
|
"scripts": 0,
|
|
"functions_total": 0,
|
|
"functions_covered": 0,
|
|
}
|
|
|
|
# (url, start, end) -> covered? Set semantics dedup the same function
|
|
# appearing in many test dumps.
|
|
seen: dict[tuple[str, int, int], bool] = {}
|
|
scripts_seen: set[str] = set()
|
|
|
|
for path in files:
|
|
try:
|
|
entries = json.loads(path.read_text())
|
|
except (OSError, json.JSONDecodeError):
|
|
continue
|
|
for entry in entries:
|
|
url = entry.get("url", "")
|
|
if not _is_app_url(url):
|
|
continue
|
|
scripts_seen.add(url)
|
|
for fn in entry.get("functions", []):
|
|
ranges = fn.get("ranges") or []
|
|
if not ranges:
|
|
continue
|
|
outer = ranges[0]
|
|
key = (
|
|
url,
|
|
int(outer.get("startOffset", 0)),
|
|
int(outer.get("endOffset", 0)),
|
|
)
|
|
covered = int(outer.get("count", 0)) > 0
|
|
# Promote to covered if any dump exercised it.
|
|
seen[key] = seen.get(key, False) or covered
|
|
|
|
return {
|
|
"tests": len(files),
|
|
"scripts": len(scripts_seen),
|
|
"functions_total": len(seen),
|
|
"functions_covered": sum(1 for v in seen.values() if v),
|
|
}
|
|
|
|
|
|
def write_vitest_summary(stats: dict, out_path: Path) -> None:
|
|
total = stats["functions_total"]
|
|
covered = stats["functions_covered"]
|
|
pct = 100.0 * covered / total if total else 0.0
|
|
# vitest coverage-summary.json schema. We report the function metric
|
|
# under both `functions` and `branches` so the helper script's
|
|
# "trust functions/branches" line still applies. Lines/Statements
|
|
# left at 0/0 so the renderer's degradation note kicks in.
|
|
payload = {
|
|
"total": {
|
|
"functions": {
|
|
"covered": covered,
|
|
"total": total,
|
|
"pct": pct,
|
|
"skipped": 0,
|
|
},
|
|
"branches": {
|
|
"covered": covered,
|
|
"total": total,
|
|
"pct": pct,
|
|
"skipped": 0,
|
|
},
|
|
"lines": {"covered": 0, "total": 0, "pct": 0.0, "skipped": 0},
|
|
"statements": {"covered": 0, "total": 0, "pct": 0.0, "skipped": 0},
|
|
}
|
|
}
|
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
out_path.write_text(json.dumps(payload, indent=2))
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"dump_dir",
|
|
type=Path,
|
|
help="Directory containing per-test V8 JSON dumps from the Playwright fixture.",
|
|
)
|
|
parser.add_argument(
|
|
"--out",
|
|
type=Path,
|
|
required=True,
|
|
help="Path to write the vitest-shaped coverage-summary.json",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
if not args.dump_dir.exists():
|
|
print(
|
|
f"::warning::No Playwright coverage dump dir at {args.dump_dir}",
|
|
file=sys.stderr,
|
|
)
|
|
write_vitest_summary({"functions_total": 0, "functions_covered": 0}, args.out)
|
|
return 0
|
|
|
|
stats = aggregate(args.dump_dir)
|
|
write_vitest_summary(stats, args.out)
|
|
pct = 100.0 * stats["functions_covered"] / stats["functions_total"] if stats["functions_total"] else 0.0
|
|
print(
|
|
f"Aggregated {stats['tests']} tests / {stats['scripts']} scripts: "
|
|
f"{stats['functions_covered']}/{stats['functions_total']} functions "
|
|
f"({pct:.1f}%)"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|