Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/autograding/coverage-annotations-ai.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"coverage": [
{
"name": "AI Coverage Annotations",
"sourcePath": "ai/src/main/java",
"maxScore": 100,
"coveredPercentageImpact": 1,
"missedPercentageImpact": 0,
"tools": [
{
"id": "jacoco",
"name": "Line Coverage",
"metric": "line",
"sourcePath": "ai/src/main/java",
"pattern": "**/pr-artifacts/ai/build/reports/kover/report.xml"
},
{
"id": "jacoco",
"name": "Branch Coverage",
"metric": "branch",
"sourcePath": "ai/src/main/java",
"pattern": "**/pr-artifacts/ai/build/reports/kover/report.xml"
}
]
}
]
}
27 changes: 27 additions & 0 deletions .github/autograding/coverage-annotations-app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"coverage": [
{
"name": "App Coverage Annotations",
"sourcePath": "app/src/main/java",
"maxScore": 100,
"coveredPercentageImpact": 1,
"missedPercentageImpact": 0,
"tools": [
{
"id": "jacoco",
"name": "Line Coverage",
"metric": "line",
"sourcePath": "app/src/main/java",
"pattern": "**/pr-artifacts/app/build/reports/kover/report.xml"
},
{
"id": "jacoco",
"name": "Branch Coverage",
"metric": "branch",
"sourcePath": "app/src/main/java",
"pattern": "**/pr-artifacts/app/build/reports/kover/report.xml"
}
]
}
]
}
27 changes: 27 additions & 0 deletions .github/autograding/coverage-annotations-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"coverage": [
{
"name": "Data Coverage Annotations",
"sourcePath": "data/src/main/java",
"maxScore": 100,
"coveredPercentageImpact": 1,
"missedPercentageImpact": 0,
"tools": [
{
"id": "jacoco",
"name": "Line Coverage",
"metric": "line",
"sourcePath": "data/src/main/java",
"pattern": "**/pr-artifacts/data/build/reports/kover/report.xml"
},
{
"id": "jacoco",
"name": "Branch Coverage",
"metric": "branch",
"sourcePath": "data/src/main/java",
"pattern": "**/pr-artifacts/data/build/reports/kover/report.xml"
}
]
}
]
}
27 changes: 27 additions & 0 deletions .github/autograding/coverage-annotations-domain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"coverage": [
{
"name": "Domain Coverage Annotations",
"sourcePath": "domain/src/main/java",
"maxScore": 100,
"coveredPercentageImpact": 1,
"missedPercentageImpact": 0,
"tools": [
{
"id": "jacoco",
"name": "Line Coverage",
"metric": "line",
"sourcePath": "domain/src/main/java",
"pattern": "**/pr-artifacts/domain/build/reports/kover/report.xml"
},
{
"id": "jacoco",
"name": "Branch Coverage",
"metric": "branch",
"sourcePath": "domain/src/main/java",
"pattern": "**/pr-artifacts/domain/build/reports/kover/report.xml"
}
]
}
]
}
24 changes: 24 additions & 0 deletions .github/quality-monitor/coverage-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"coverage": [
{
"name": "Line Coverage",
"tools": [
{
"id": "jacoco",
"metric": "line",
"pattern": "**/pr-artifacts/build/reports/kover/report.xml"
}
]
},
{
"name": "Instruction Coverage",
"tools": [
{
"id": "jacoco",
"metric": "instruction",
"pattern": "**/pr-artifacts/build/reports/kover/report.xml"
}
]
}
]
}
29 changes: 0 additions & 29 deletions .github/scripts/pr/build_pr_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,6 @@ def count_sarif_results(files: list[Path]) -> int:
return count


def aggregate_line_coverage(files: list[Path]) -> tuple[int, int]:
covered = 0
missed = 0
for file in files:
try:
root = ET.parse(file).getroot()
except ET.ParseError:
continue

for counter in root.findall("./counter"):
if counter.attrib.get("type") == "LINE":
covered += int(counter.attrib.get("covered", "0"))
missed += int(counter.attrib.get("missed", "0"))
break
return covered, missed


def format_test_line(label: str, metrics: dict[str, int]) -> str:
total_failures = metrics["failures"] + metrics["errors"]
status = "PASS" if total_failures == 0 else "FAIL"
Expand All @@ -104,7 +87,6 @@ def main() -> int:
instrumentation_xml = sorted(artifacts_dir.glob("**/instrumentation-results.xml"))
lint_xml = sorted(artifacts_dir.glob("**/build/reports/lint-results-*.xml"))
ktlint_xml = sorted(artifacts_dir.glob("**/build/reports/ktlint/**/*.xml"))
kover_xml = sorted(artifacts_dir.glob("**/build/reports/kover/**/*.xml"))
detekt_sarif = sorted(artifacts_dir.glob("**/build/reports/detekt/*.sarif"))
gitleaks_sarif = sorted(artifacts_dir.glob("**/build/reports/gitleaks/*.sarif"))

Expand All @@ -114,10 +96,6 @@ def main() -> int:
ktlint_issues = count_ktlint_issues(ktlint_xml)
detekt_findings = count_sarif_results(detekt_sarif)
gitleaks_findings = count_sarif_results(gitleaks_sarif)
covered, missed = aggregate_line_coverage(kover_xml)
total_lines = covered + missed
coverage = (covered / total_lines * 100.0) if total_lines else None

pr_number = sys.argv[3]
head_sha = sys.argv[4]
run_url = sys.argv[5]
Expand Down Expand Up @@ -145,13 +123,6 @@ def main() -> int:
f"- detekt: {detekt_findings} finding(s) uploaded via SARIF" if detekt_sarif else "- detekt: no SARIF reports found",
f"- gitleaks: {gitleaks_findings} finding(s) uploaded via SARIF" if gitleaks_sarif else "- gitleaks: no SARIF reports found",
"- CodeQL: see the Code Scanning section in this PR",
"",
"### Coverage",
(
f"- Line coverage: {coverage:.2f}% ({covered} covered / {total_lines} total)"
if coverage is not None
else "- Line coverage: no Kover XML reports found"
),
]

output_file.write_text("\n".join(line for line in lines if line is not None) + "\n")
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/pr/list_downloaded_artifacts.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail

find "${1:-pr-artifacts}" -maxdepth 4 -type f | sort
find "${1:-pr-artifacts}" -maxdepth 8 -type f | sort
163 changes: 163 additions & 0 deletions .github/scripts/quality/build_coverage_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#!/usr/bin/env python3
from __future__ import annotations

import sys
import xml.etree.ElementTree as ET
from pathlib import Path


MetricCounts = dict[str, tuple[int, int]]
METRICS: list[tuple[str, str]] = [
("LINE", "Line"),
("INSTRUCTION", "Instruction"),
("BRANCH", "Branch"),
("METHOD", "Method"),
("CLASS", "Class"),
]


def read_counters(report: Path) -> MetricCounts | None:
try:
root = ET.parse(report).getroot()
except ET.ParseError:
return None

counters: MetricCounts = {}
for counter in root.findall("./counter"):
metric = counter.attrib.get("type")
if metric is None:
continue
counters[metric] = (
int(counter.attrib.get("covered", "0")),
int(counter.attrib.get("missed", "0")),
)

return counters or None


def format_ratio(covered: int, missed: int) -> str:
total = covered + missed
if total == 0:
return "n/a"
return f"{covered / total * 100:.2f}%"


def format_metric_cell(counts: MetricCounts, metric: str) -> str:
covered, missed = counts.get(metric, (0, 0))
total = covered + missed
if total == 0:
return "n/a (0/0)"
return f"{format_ratio(covered, missed)} ({covered}/{total})"


def render_table_row(cells: list[str]) -> str:
return f"| {' | '.join(cells)} |"


def discover_reports(reports_root: Path) -> tuple[MetricCounts | None, list[tuple[str, MetricCounts]]]:
aggregate_report = reports_root / "build" / "reports" / "kover" / "report.xml"
if aggregate_report.exists():
aggregate_counts = read_counters(aggregate_report)
module_rows: list[tuple[str, MetricCounts]] = []
for report in reports_root.glob("*/build/reports/kover/report.xml"):
counts = read_counters(report)
if counts is None:
continue
module_rows.append((report.relative_to(reports_root).parts[0], counts))
return aggregate_counts, module_rows

aggregate_counts = None
module_rows = []
fallback_rows = []

for report in sorted(reports_root.glob("**/build/reports/kover/report.xml")):
Comment thread
allnes marked this conversation as resolved.
counts = read_counters(report)
if counts is None:
continue

rel = report.relative_to(reports_root)
parts = rel.parts

if parts == ("build", "reports", "kover", "report.xml"):
aggregate_counts = counts
elif len(parts) >= 5 and parts[1:5] == ("build", "reports", "kover", "report.xml"):
module_rows.append((parts[0], counts))
else:
fallback_rows.append((str(rel.parent), counts))

return aggregate_counts, module_rows or fallback_rows


def main() -> int:
if len(sys.argv) != 3:
print("usage: build_coverage_summary.py <reports_root> <output_file>", file=sys.stderr)
return 2

reports_root = Path(sys.argv[1])
output_file = Path(sys.argv[2])

aggregate_counts, rows = discover_reports(reports_root)

if aggregate_counts is None and rows:
aggregate_counts = {}
for metric, _ in METRICS:
covered = sum(counts.get(metric, (0, 0))[0] for _, counts in rows)
missed = sum(counts.get(metric, (0, 0))[1] for _, counts in rows)
aggregate_counts[metric] = (covered, missed)

lines = [
"## Coverage Details",
"",
]

if aggregate_counts is not None:
lines.extend(
[
"",
"### Overall",
"",
"| Metric | Coverage | Covered | Missed | Total |",
"| --- | ---: | ---: | ---: | ---: |",
]
)

for metric, label in METRICS:
covered, missed = aggregate_counts.get(metric, (0, 0))
total = covered + missed
lines.append(
render_table_row([label, format_ratio(covered, missed), str(covered), str(missed), str(total)])
)

if rows:
lines.extend(
[
"",
"### By Module",
"",
"| Module | Line | Instruction | Branch | Method | Class |",
"| --- | ---: | ---: | ---: | ---: | ---: |",
]
)

for module, counts in sorted(rows):
lines.append(
render_table_row(
[
f"`{module}`",
format_metric_cell(counts, "LINE"),
format_metric_cell(counts, "INSTRUCTION"),
format_metric_cell(counts, "BRANCH"),
format_metric_cell(counts, "METHOD"),
format_metric_cell(counts, "CLASS"),
]
)
)
elif aggregate_counts is None:
lines.extend(["", "- No Kover XML reports found"])

output_file.write_text("\n".join(lines) + "\n", encoding="utf-8")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading