Introduce contracted compiled patch tries for faster lookup, make compiled patch commands the primary runtime path, refresh stemmer benchmarks and documentation, and restructure the documentation for 3.0.0 onboarding. BREAKING CHANGE: Radixor 3.0.0 promotes compiled patch-command APIs and new compiled trie artifacts as the primary runtime integration model.
178 lines
5.0 KiB
Python
Executable File
178 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Generate GitHub Pages badge endpoint JSON files from CI report artifacts.
|
|
|
|
This script derives compact machine-readable badge payloads from:
|
|
|
|
- JaCoCo XML coverage report
|
|
- PIT mutation testing XML report
|
|
|
|
The generated JSON files are intended to be consumed by Shields endpoint badges.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import xml.etree.ElementTree as ET
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
"""Parse command-line arguments."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate GitHub Pages badge metadata from build reports."
|
|
)
|
|
parser.add_argument(
|
|
"--jacoco-xml",
|
|
required=True,
|
|
help="Path to the JaCoCo XML report."
|
|
)
|
|
parser.add_argument(
|
|
"--pit-xml",
|
|
required=True,
|
|
help="Path to the PIT XML report."
|
|
)
|
|
parser.add_argument(
|
|
"--jmh-csv",
|
|
required=False,
|
|
help="Deprecated compatibility option. JMH speed badges are no longer generated."
|
|
)
|
|
parser.add_argument(
|
|
"--run-metrics-dir",
|
|
required=True,
|
|
help="Target directory for the current build badge JSON files."
|
|
)
|
|
parser.add_argument(
|
|
"--latest-metrics-dir",
|
|
required=True,
|
|
help="Target directory for the latest build badge JSON files."
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def write_json(target: Path, payload: dict[str, object]) -> None:
|
|
"""Write a badge payload as formatted UTF-8 JSON."""
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_text(json.dumps(payload, indent=2) + os.linesep, encoding="utf-8")
|
|
|
|
|
|
def remove_file_if_present(target: Path) -> None:
|
|
"""Remove a previously generated file when it is present."""
|
|
if target.is_file():
|
|
target.unlink()
|
|
|
|
|
|
def unavailable_payload(label: str) -> dict[str, object]:
|
|
"""Create a standard payload for unavailable metrics."""
|
|
return {
|
|
"schemaVersion": 1,
|
|
"label": label,
|
|
"message": "not available",
|
|
"color": "lightgrey"
|
|
}
|
|
|
|
|
|
def color_for_percentage(value: float) -> str:
|
|
"""Select a badge color for a percentage value."""
|
|
if value >= 85.0:
|
|
return "brightgreen"
|
|
if value >= 70.0:
|
|
return "green"
|
|
if value >= 55.0:
|
|
return "yellow"
|
|
if value >= 40.0:
|
|
return "orange"
|
|
return "red"
|
|
|
|
|
|
def coverage_payload(jacoco_xml: Path) -> dict[str, object]:
|
|
"""Build a line coverage badge payload from a JaCoCo XML report."""
|
|
if not jacoco_xml.is_file():
|
|
return unavailable_payload("coverage")
|
|
|
|
root = ET.parse(jacoco_xml).getroot()
|
|
line_counter = None
|
|
for counter in root.findall("counter"):
|
|
if counter.attrib.get("type") == "LINE":
|
|
line_counter = counter
|
|
break
|
|
|
|
if line_counter is None:
|
|
return unavailable_payload("coverage")
|
|
|
|
missed = int(line_counter.attrib.get("missed", "0"))
|
|
covered = int(line_counter.attrib.get("covered", "0"))
|
|
total = missed + covered
|
|
percentage = 0.0 if total == 0 else (100.0 * covered / total)
|
|
|
|
return {
|
|
"schemaVersion": 1,
|
|
"label": "coverage",
|
|
"message": f"{percentage:.1f}%",
|
|
"color": color_for_percentage(percentage)
|
|
}
|
|
|
|
|
|
def mutation_payload(pit_xml: Path) -> dict[str, object]:
|
|
"""Build a mutation score badge payload from a PIT XML report."""
|
|
if not pit_xml.is_file():
|
|
return unavailable_payload("mutation")
|
|
|
|
root = ET.parse(pit_xml).getroot()
|
|
mutation_coverage = root.attrib.get("mutationCoverage")
|
|
if mutation_coverage is not None:
|
|
score = float(mutation_coverage)
|
|
else:
|
|
detected_statuses = {
|
|
"KILLED",
|
|
"TIMED_OUT",
|
|
"MEMORY_ERROR",
|
|
"RUN_ERROR",
|
|
"NON_VIABLE"
|
|
}
|
|
mutations = root.findall("mutation")
|
|
total = len(mutations)
|
|
detected = sum(
|
|
1
|
|
for mutation in mutations
|
|
if mutation.attrib.get("status") in detected_statuses
|
|
)
|
|
score = 0.0 if total == 0 else (100.0 * detected / total)
|
|
|
|
return {
|
|
"schemaVersion": 1,
|
|
"label": "mutation",
|
|
"message": f"{score:.1f}%",
|
|
"color": color_for_percentage(score)
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
"""Generate all requested badge metadata files."""
|
|
arguments = parse_args()
|
|
|
|
jacoco_xml = Path(arguments.jacoco_xml)
|
|
pit_xml = Path(arguments.pit_xml)
|
|
run_metrics_dir = Path(arguments.run_metrics_dir)
|
|
latest_metrics_dir = Path(arguments.latest_metrics_dir)
|
|
|
|
payloads = {
|
|
"coverage-badge.json": coverage_payload(jacoco_xml),
|
|
"pitest-badge.json": mutation_payload(pit_xml)
|
|
}
|
|
|
|
for file_name, payload in payloads.items():
|
|
write_json(run_metrics_dir / file_name, payload)
|
|
write_json(latest_metrics_dir / file_name, payload)
|
|
|
|
remove_file_if_present(run_metrics_dir / "jmh-badge.json")
|
|
remove_file_if_present(latest_metrics_dir / "jmh-badge.json")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|