CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

testland/lcov-analysis

Use when a coverage gate must work across languages from LCOV .info or Cobertura XML files (gcov, llvm-cov, coverage.py, JaCoCo, Jest, nyc, coverlet). Parses per-file line, function and branch metrics, compares with a baseline and returns per-file pass or fail verdicts.

Quality

88%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Passed

No findings from the security scan

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