Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Codecov configuration.
#
# Coverage is produced by the `clang-coverage` CI job via scripts/coverage.sh,
# restricted to the library headers under include/morph (tests, demo src/ and
# fetched dependencies are excluded by the positional source filter to llvm-cov).

coverage:
# Statuses are informational so a coverage delta never blocks a PR; they still
# render the project/patch numbers on the checks list.
status:
project:
default:
informational: true
patch:
default:
informational: true

# Always post the coverage-comparison comment on a PR, even on the first upload
# after activation and even when the base report is still processing.
comment:
layout: "reference, diff, flags, files"
behavior: default
require_base: false
require_head: true
require_changes: false

# Only library headers carry coverage; make the exclusion explicit for Codecov's
# own file walking so tests/ and the demo never dilute the reported number.
ignore:
- "tests/**"
- "src/**"
- "examples/**"
79 changes: 79 additions & 0 deletions scripts/aggregate_lcov_branches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Rewrite an lcov file's branch records to llvm-cov's *aggregate* coverage.

`llvm-cov export -format=lcov` emits branch data (BRDA) once per template
instantiation. A branch that is fully covered in aggregate is therefore still
reported with an untaken arm for every instantiation that happened not to take
it, and Codecov scores each such line as "partial". For header-only templated
code (e.g. completion.hpp, bridge.hpp) this manufactures dozens of spurious
partials even though `llvm-cov report` shows the files at ~100% branch coverage.

This script keeps branch coverage (we do NOT drop it) but collapses the
per-instantiation records into one record per *source branch*, keyed by the
branch's source location taken from the JSON export. The result mirrors exactly
what `llvm-cov report` counts, so genuinely-uncovered branches stay uncovered
and only the per-instantiation noise disappears.

Usage: aggregate_lcov_branches.py <in.lcov> <cov.json> <out.lcov>
"""
import json
import sys
from collections import defaultdict, OrderedDict


def load_branch_aggregate(json_path):
"""file-basename-independent: map absolute filename -> {line: [taken_arms...]}."""
data = json.load(open(json_path))
per_file = {}
for f in data["data"][0]["files"]:
# Aggregate each source branch (identified by its start/end location)
# across every instantiation, summing the true/false execution counts.
agg = defaultdict(lambda: [0, 0])
for b in f.get("branches", []):
key = (b[0], b[1], b[2], b[3]) # l0, c0, l1, c1
agg[key][0] += b[4] # true count
agg[key][1] += b[5] # false count
by_line = defaultdict(list)
for (l0, c0, _l1, c0b), (t, fc) in sorted(agg.items()):
by_line[l0].append((c0, c0b, t, fc))
per_file[f["filename"]] = by_line
return per_file


def main():
in_lcov, json_path, out_lcov = sys.argv[1], sys.argv[2], sys.argv[3]
branches = load_branch_aggregate(json_path)

out = []
cur = None
for raw in open(in_lcov):
line = raw.rstrip("\n")
if line.startswith("SF:"):
cur = line[3:]
out.append(line)
elif line.startswith(("BRDA:", "BRF:", "BRH:")):
continue # drop per-instantiation branch data; re-emitted below
elif line == "end_of_record":
by_line = branches.get(cur, {})
brf = brh = 0
idx = 0
for ln in sorted(by_line):
for (_c0, _c0b, t, fc) in by_line[ln]:
for taken in (t, fc):
out.append(f"BRDA:{ln},0,{idx},{taken}")
idx += 1
brf += 1
if taken > 0:
brh += 1
if brf:
out.append(f"BRF:{brf}")
out.append(f"BRH:{brh}")
out.append(line)
else:
out.append(line)

open(out_lcov, "w").write("\n".join(out) + "\n")


if __name__ == "__main__":
main()
16 changes: 15 additions & 1 deletion scripts/coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,18 @@ llvm-cov-20 export "$TEST_EXE" \
-instr-profile="$MERGED" \
-format=lcov \
"$SOURCES" \
> "$OUT/coverage.lcov"
> "$OUT/coverage.lcov.raw"

# llvm-cov emits branch (BRDA) records once per template instantiation, so a
# branch that is covered in aggregate is still scored "partial" by Codecov for
# every instantiation that did not take one arm — dozens of spurious partials on
# header-only templated code. Collapse them to one record per source branch,
# matching the aggregate that `llvm-cov report` already prints above. Branch
# coverage is preserved (not skipped); only the per-instantiation noise is removed.
llvm-cov-20 export "$TEST_EXE" \
-instr-profile="$MERGED" \
"$SOURCES" \
> "$OUT/coverage.json"

python3 scripts/aggregate_lcov_branches.py \
"$OUT/coverage.lcov.raw" "$OUT/coverage.json" "$OUT/coverage.lcov"
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_executable(morph_tests
test_subscription.cpp
test_coverage_gaps.cpp
test_coverage_extra.cpp
test_coverage_push95.cpp
test_server_limits.cpp
)

Expand Down
Loading
Loading