diff --git a/testing/ci/publish_metrics.py b/testing/ci/publish_metrics.py index 2039c01..7ccb145 100644 --- a/testing/ci/publish_metrics.py +++ b/testing/ci/publish_metrics.py @@ -29,11 +29,11 @@ def _build_parser() -> argparse.ArgumentParser: return parser -def _load_quality_report(path: Path) -> tuple[float, int, dict[str, str]]: +def _load_quality_report(path: Path) -> tuple[float, int, int, dict[str, str]]: """Read workspace coverage/LOC summary from the quality gate JSON output.""" if not path.exists(): - return 0.0, 0, { + return 0.0, 0, 0, { "tests": "not_applicable", "coverage": "not_applicable", "loc": "not_applicable", @@ -49,6 +49,10 @@ def _load_quality_report(path: Path) -> tuple[float, int, dict[str, str]]: source_lines = payload.get("source_lines_over_500") if not isinstance(source_lines, int): source_lines = 0 + source_files = payload.get("source_files_total") + if not isinstance(source_files, int): + managed_files = payload.get("managed_files") + source_files = len(managed_files) if isinstance(managed_files, list) else 0 issue_checks = [item.get("check") for item in payload.get("issues", []) if isinstance(item, dict)] docs_failed = any(str(check).lower() in {"docstring", "docs", "naming"} for check in issue_checks) coverage_failed = any(str(check).lower() == "coverage" for check in issue_checks) @@ -95,7 +99,7 @@ def _load_quality_report(path: Path) -> tuple[float, int, dict[str, str]]: checks["supply_chain"] = "failed" if ironbank_required else "not_applicable" except Exception: checks["supply_chain"] = "failed" if ironbank_required else "not_applicable" - return float(coverage), int(source_lines), checks + return float(coverage), int(source_files), int(source_lines), checks def main(argv: list[str] | None = None) -> int: @@ -106,7 +110,7 @@ def main(argv: list[str] | None = None) -> int: junit_paths = [Path(path) for path in args.junit] summary = load_junit_summary(junit_paths) test_cases = load_junit_cases(junit_paths) - coverage_percent, source_lines_over_500, checks = _load_quality_report(Path(args.quality_report)) + coverage_percent, source_files_total, source_lines_over_500, checks = _load_quality_report(Path(args.quality_report)) publish_quality_metrics( gateway=args.gateway, suite=args.suite, @@ -114,6 +118,7 @@ def main(argv: list[str] | None = None) -> int: status=args.status, summary=summary, workspace_line_coverage_percent=coverage_percent, + source_files_total=source_files_total, source_lines_over_500=source_lines_over_500, branch=args.branch, build_number=args.build_number, diff --git a/testing/ci/quality_gate.py b/testing/ci/quality_gate.py index 7c16b54..8813e2d 100644 --- a/testing/ci/quality_gate.py +++ b/testing/ci/quality_gate.py @@ -330,6 +330,9 @@ def run_gate(contract_path: Path, *, backend_coverage: Path, frontend_coverage: frontend_report=frontend_coverage, ) workspace_line_coverage_percent = round(sum(coverage_values) / len(coverage_values), 3) if coverage_values else 0.0 + source_files_total = sum( + 1 for path in managed_files if path.exists() and path.suffix.lower() in TEXT_EXTENSIONS + ) source_lines_over_500 = sum(1 for issue in issues if issue.check == "loc") report = { "managed_files": [str(path.relative_to(ROOT)) for path in managed_files], @@ -338,6 +341,7 @@ def run_gate(contract_path: Path, *, backend_coverage: Path, frontend_coverage: "max_lines": max_lines, "coverage_threshold_pct": threshold, "workspace_line_coverage_percent": workspace_line_coverage_percent, + "source_files_total": source_files_total, "source_lines_over_500": source_lines_over_500, "issue_count": len(issues), "issues": [issue.__dict__ for issue in issues], diff --git a/testing/ci/summary.py b/testing/ci/summary.py index ae90c97..970208d 100644 --- a/testing/ci/summary.py +++ b/testing/ci/summary.py @@ -124,6 +124,7 @@ def render_payload( failed: int, summary: RunSummary, workspace_line_coverage_percent: float = 0.0, + source_files_total: int = 0, source_lines_over_500: int = 0, branch: str = "", build_number: str = "", @@ -150,6 +151,8 @@ def render_payload( f'bstein_home_quality_gate_tests_total{{suite="{suite}",result="skipped"}} {summary.skipped}\n' "# TYPE platform_quality_gate_workspace_line_coverage_percent gauge\n" f'platform_quality_gate_workspace_line_coverage_percent{{suite="{suite}"}} {workspace_line_coverage_percent:.3f}\n' + "# TYPE platform_quality_gate_source_files_total gauge\n" + f'platform_quality_gate_source_files_total{{suite="{suite}"}} {int(source_files_total)}\n' "# TYPE platform_quality_gate_source_lines_over_500_total gauge\n" f'platform_quality_gate_source_lines_over_500_total{{suite="{suite}"}} {int(source_lines_over_500)}\n' "# TYPE platform_quality_gate_build_info gauge\n" @@ -183,6 +186,7 @@ def publish_quality_metrics( status: str, summary: RunSummary, workspace_line_coverage_percent: float = 0.0, + source_files_total: int = 0, source_lines_over_500: int = 0, branch: str = "", build_number: str = "", @@ -218,6 +222,7 @@ def publish_quality_metrics( failed=int(counters["failed"]), summary=summary, workspace_line_coverage_percent=workspace_line_coverage_percent, + source_files_total=source_files_total, source_lines_over_500=source_lines_over_500, branch=branch, build_number=build_number, diff --git a/testing/tests/test_publish_metrics.py b/testing/tests/test_publish_metrics.py index 2ae47c1..8dd667d 100644 --- a/testing/tests/test_publish_metrics.py +++ b/testing/tests/test_publish_metrics.py @@ -25,6 +25,7 @@ def test_load_junit_summary_combines_suites(tmp_path: Path) -> None: assert 'platform_quality_gate_runs_total{suite="bstein_home",status="ok"} 2' in payload assert 'bstein_home_quality_gate_tests_total{suite="bstein_home",result="skipped"} 1' in payload assert 'platform_quality_gate_workspace_line_coverage_percent{suite="bstein_home"} 0.000' in payload + assert 'platform_quality_gate_source_files_total{suite="bstein_home"} 0' in payload assert 'platform_quality_gate_source_lines_over_500_total{suite="bstein_home"} 0' in payload diff --git a/testing/tests/test_quality_gate.py b/testing/tests/test_quality_gate.py index fe523cb..a6151dc 100644 --- a/testing/tests/test_quality_gate.py +++ b/testing/tests/test_quality_gate.py @@ -160,6 +160,7 @@ def test_run_gate_reports_workspace_coverage_and_loc_totals(tmp_path: Path) -> N assert any(issue.check == "loc" for issue in issues) assert report["workspace_line_coverage_percent"] == 100.0 + assert report["source_files_total"] == 2 assert report["source_lines_over_500"] == 1 finally: quality_gate_module.ROOT = root_before