Replace argument recovery with direct build targets. Run host, target, board, coverage, Miri and selected Kani checks while retaining source identities, logs and artifacts, including failed attempts. Keep the PID proof diagnostic exception explicit and match its exact identity alongside passing contract assertions and covers. Validation: final-source verification matrix, assembled workflow tests and documentation build.
208 lines
7.7 KiB
Python
208 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Summarize strict-core LCOV lines and LLVM JSON branches by source location.
|
|
|
|
Merge generic monomorphizations by source location. A location is covered if
|
|
any instance executed it:
|
|
|
|
* lines use LCOV `DA` file and line numbers
|
|
* branch outcomes use their full source spans
|
|
|
|
JSON segments mark instrumentation boundaries and omit some `DA` lines.
|
|
Using them would undercount the line denominator.
|
|
|
|
Reject macro expansions: invocation spans cannot distinguish expanded predicates.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from collections.abc import Iterable
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
class CoverageFormatError(ValueError):
|
|
"""Coverage records cannot be safely summarized by source location."""
|
|
|
|
|
|
def selected_files(
|
|
report: dict[object, object], source_path: str
|
|
) -> Iterable[dict[object, object]]:
|
|
"""Yield selected file records after rejecting ambiguous macro expansion."""
|
|
exports = report.get("data")
|
|
if not isinstance(exports, list):
|
|
raise CoverageFormatError("LLVM JSON has no data array")
|
|
|
|
for export in exports:
|
|
if not isinstance(export, dict):
|
|
raise CoverageFormatError("LLVM JSON data array contains a non-object")
|
|
files = export.get("files")
|
|
if not isinstance(files, list):
|
|
raise CoverageFormatError("LLVM JSON export has no files array")
|
|
for file_record in files:
|
|
if not isinstance(file_record, dict):
|
|
raise CoverageFormatError("LLVM JSON files array contains a non-object")
|
|
filename = file_record.get("filename")
|
|
if not isinstance(filename, str) or source_path not in filename:
|
|
continue
|
|
expansions = file_record.get("expansions", [])
|
|
if not isinstance(expansions, list):
|
|
raise CoverageFormatError(f"{filename}: expansions is not an array")
|
|
if expansions:
|
|
raise CoverageFormatError(
|
|
f"{filename}: macro-expansion coverage is present; "
|
|
"refusing to merge it by invocation source span"
|
|
)
|
|
yield file_record
|
|
|
|
|
|
def lcov_line_totals(report: str, source_path: str) -> tuple[int, int, set[str]]:
|
|
"""Return the union of selected LCOV `DA` lines and their source files."""
|
|
lines: dict[tuple[str, int], bool] = {}
|
|
files: set[str] = set()
|
|
filename: str | None = None
|
|
|
|
for record in report.splitlines():
|
|
if record.startswith("SF:"):
|
|
filename = record[3:]
|
|
if source_path in filename:
|
|
files.add(filename)
|
|
continue
|
|
if record == "end_of_record":
|
|
filename = None
|
|
continue
|
|
if (
|
|
filename is None
|
|
or source_path not in filename
|
|
or not record.startswith("DA:")
|
|
):
|
|
continue
|
|
fields = record[3:].split(",", 2)
|
|
if len(fields) < 2:
|
|
raise CoverageFormatError(f"{filename}: malformed LCOV DA record")
|
|
try:
|
|
line = int(fields[0])
|
|
count = int(fields[1])
|
|
except ValueError as error:
|
|
raise CoverageFormatError(
|
|
f"{filename}: non-integer LCOV DA record"
|
|
) from error
|
|
if line <= 0 or count < 0:
|
|
raise CoverageFormatError(f"{filename}: invalid LCOV DA location or count")
|
|
key = (filename, line)
|
|
lines[key] = lines.get(key, False) or count > 0
|
|
|
|
if not files:
|
|
raise CoverageFormatError(f"no LCOV source file contains {source_path!r}")
|
|
if not lines:
|
|
raise CoverageFormatError(f"no LCOV DA records found under {source_path!r}")
|
|
return sum(lines.values()), len(lines), files
|
|
|
|
|
|
def branch_totals(
|
|
report: dict[object, object], source_path: str
|
|
) -> tuple[int, int, set[str]]:
|
|
"""Return the union of selected JSON branch outcomes and source files."""
|
|
branches: dict[tuple[str, int, int, int, int], list[bool]] = {}
|
|
files: set[str] = set()
|
|
|
|
for file_record in selected_files(report, source_path):
|
|
filename = file_record["filename"]
|
|
assert isinstance(filename, str)
|
|
files.add(filename)
|
|
|
|
raw_branches = file_record.get("branches")
|
|
if not isinstance(raw_branches, list):
|
|
raise CoverageFormatError(f"{filename}: branches is not an array")
|
|
for branch in raw_branches:
|
|
if (
|
|
not isinstance(branch, list)
|
|
or len(branch) != 9
|
|
or not all(type(value) is int for value in branch)
|
|
):
|
|
raise CoverageFormatError(f"{filename}: invalid branch record")
|
|
(
|
|
start_line,
|
|
start_column,
|
|
end_line,
|
|
end_column,
|
|
true_count,
|
|
false_count,
|
|
file_id,
|
|
expanded_file_id,
|
|
_kind,
|
|
) = branch
|
|
if (
|
|
start_line <= 0
|
|
or start_column <= 0
|
|
or end_line < start_line
|
|
or end_column <= 0
|
|
or (end_line == start_line and end_column < start_column)
|
|
or true_count < 0
|
|
or false_count < 0
|
|
or file_id < 0
|
|
or expanded_file_id < 0
|
|
):
|
|
raise CoverageFormatError(
|
|
f"{filename}: invalid branch location or count"
|
|
)
|
|
if file_id != expanded_file_id:
|
|
raise CoverageFormatError(
|
|
f"{filename}:{start_line}:{start_column}: macro-expanded branch is present; "
|
|
"refusing to merge it by invocation source span"
|
|
)
|
|
key = (filename, start_line, start_column, end_line, end_column)
|
|
outcomes = branches.setdefault(key, [False, False])
|
|
outcomes[0] = outcomes[0] or true_count > 0
|
|
outcomes[1] = outcomes[1] or false_count > 0
|
|
|
|
if not files:
|
|
raise CoverageFormatError(f"no LLVM JSON source file contains {source_path!r}")
|
|
branch_hit = sum(sum(outcomes) for outcomes in branches.values())
|
|
return branch_hit, 2 * len(branches), files
|
|
|
|
|
|
def coverage_totals(
|
|
lcov_report: str, json_report: dict[object, object], source_path: str
|
|
) -> tuple[int, int, int, int]:
|
|
"""Return strict source-level line and branch coverage for one core crate."""
|
|
lines_hit, lines_found, lcov_files = lcov_line_totals(lcov_report, source_path)
|
|
branch_hit, branches_found, json_files = branch_totals(json_report, source_path)
|
|
if lcov_files != json_files:
|
|
raise CoverageFormatError(
|
|
"LCOV/JSON source-file mismatch: "
|
|
f"LCOV={sorted(lcov_files)!r}, JSON={sorted(json_files)!r}"
|
|
)
|
|
return lines_hit, lines_found, branch_hit, branches_found
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"lcov_report", type=Path, help="LCOV report from cargo llvm-cov"
|
|
)
|
|
parser.add_argument(
|
|
"json_report", type=Path, help="LLVM JSON report from cargo llvm-cov"
|
|
)
|
|
parser.add_argument(
|
|
"source_path", help="workspace-relative path fragment to include"
|
|
)
|
|
arguments = parser.parse_args()
|
|
|
|
try:
|
|
lcov_report = arguments.lcov_report.read_text(encoding="utf-8")
|
|
with arguments.json_report.open(encoding="utf-8") as report_file:
|
|
json_report = json.load(report_file)
|
|
if not isinstance(json_report, dict):
|
|
raise CoverageFormatError("LLVM JSON root is not an object")
|
|
print(*coverage_totals(lcov_report, json_report, arguments.source_path))
|
|
except (CoverageFormatError, OSError, json.JSONDecodeError) as error:
|
|
print(f"coverage summary error: {error}", file=sys.stderr)
|
|
return 2
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|