CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/lcov-analysis

Parses LCOV `.info` text files (the de-facto coverage interchange format produced by gcov, llvm-cov, Coverage.py via `py2lcov`, JaCoCo via `xml2lcov`, Devel::Cover, Jest via `lcov` reporter, NYC, and most others). Extracts per-file line / function / branch metrics from the canonical record keywords (TN/SF/FN/FNDA/FNF/FNH/BRDA/BRF/BRH/DA/LH/LF), computes the diff vs a baseline, and emits per-file gating verdicts. Use for PR coverage gates that don't depend on a specific language runtime.

74

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

format-diff-and-ci.mdreferences/

LCOV record format, baseline diff, and CI wiring

Deep reference for the lcov-analysis SKILL.md. Consult for the full .info record-keyword table, the baseline-diff and gate reference implementation, and the PR coverage-gate CI job.

.info record-keyword reference

Per lcov-readme, the LCOV coverage data format uses these record types:

KeywordMeaning
TN:<test>Test name (often empty for whole-suite captures).
SF:<path>Source file path (one record set per source file).
FN:<line>,<name>Function declared at <line> named <name>.
FNDA:<count>,<name>Function <name> was called <count> times.
FNF:<n>Functions found in this file.
FNH:<n>Functions hit at least once.
BRDA:<line>,<block>,<branch>,<taken>Branch coverage data.
BRF:<n>Branches found.
BRH:<n>Branches hit.
DA:<line>,<count>Line <line> was executed <count> times.
LH:<n>Lines hit.
LF:<n>Lines found.
end_of_recordMarks completion of the current source file's data.

Per record, BRDA's fourth value <taken> is the hit count for that branch arm or - if the branch was never reached (the preceding line wasn't executed).

Diff vs baseline

def coverage_diff(current, baseline):
    """For each file, compute (line%_now - line%_then), (branch%_now - branch%_then)."""
    base_by_path = {f['path']: f for f in baseline}
    out = []
    for f in current:
        b = base_by_path.get(f['path'])
        line_now = pct(f['lh'], f['lf'])
        line_then = pct(b['lh'], b['lf']) if b else None
        branch_now = pct(f['brh'], f['brf'])
        branch_then = pct(b['brh'], b['brf']) if b else None
        out.append({
            'path': f['path'],
            'line_now': line_now, 'line_then': line_then,
            'branch_now': branch_now, 'branch_then': branch_then,
            'is_new': b is None,
        })
    return out

def pct(num, denom):
    return None if denom == 0 else round(100 * num / denom, 1)

The interesting outputs are drops (line_now < line_then) and new files with sub-threshold coverage (is_new and line_now < gate).

Gate

def gate(diff, whole_drop_max=0.5, file_drop_max=5.0, new_file_min=80.0):
    failures = []
    for f in diff:
        if f['is_new'] and f['line_now'] is not None and f['line_now'] < new_file_min:
            failures.append((f['path'], 'new file below threshold', f['line_now']))
        elif f['line_then'] is not None and f['line_now'] is not None:
            drop = f['line_then'] - f['line_now']
            if drop > file_drop_max:
                failures.append((f['path'], f'line% dropped {drop:.1f}pp', drop))
    # Whole-repo drop:
    sum_then_lh = sum(f['line_then'] for f in diff if f['line_then'] is not None)
    sum_now_lh  = sum(f['line_now']  for f in diff if f['line_now']  is not None)
    return failures

CI shape

- name: Run tests with LCOV reporter
  run: npm test -- --coverage --coverageReporters=lcov

- name: Download baseline
  uses: actions/download-artifact@v4
  with:
    name: lcov-main
    path: baseline/

- name: Parse + diff + gate
  run: |
    python scripts/parse_lcov.py coverage/lcov.info > current.json
    python scripts/parse_lcov.py baseline/lcov.info > baseline.json
    python scripts/coverage_gate.py current.json baseline.json

- name: Upload current LCOV (becomes next PR's baseline when on main)
  if: github.ref == 'refs/heads/main'
  uses: actions/upload-artifact@v4
  with:
    name: lcov-main
    path: coverage/lcov.info
    retention-days: 90

Cache main's LCOV as an artifact so PRs diff against it rather than recomputing the merge base's coverage twice.

SKILL.md

tile.json