Skip to content

feat(coverage): report per-line execution hit countsΒ #856

Description

@Chemaclass

🎯 Goal

Surface per-line execution hit counts (gcov-style: how many times each line ran) in coverage reports, instead of only boolean covered/not-covered. The raw data to do this largely exists already β€” this is mostly a reporting change.

πŸ“ Where

  • Raw hit records are produced by bashunit::coverage::record_line into _BASHUNIT_COVERAGE_DATA_FILE / _BASHUNIT_COVERAGE_TEST_HITS_FILE (src/coverage.sh:294-301, flush at :336-339).
  • Report readers collapse to a covered set today: src/coverage.sh:575 (grep … | sort -u), :688, :743.
  • Report writers: src/reports.sh (HTML/JUnit/etc.) and the console coverage summary.

⚠️ Interaction with the dedup issue (read first)

The per-line dedup issue proposes collapsing repeated hits at source (one record per file:line). If that lands, hit counts are lost at the source. Two options β€” the implementer must pick and document:

  • (A) Count in memory: keep dedup, but maintain a small in-memory file:line β†’ count tally in record_line and flush counts (not repeated records). Preserves counts and the I/O win. Preferred.
  • (B) Order the work: land hit-counts first (parse repeated raw records β†’ counts at report time), then make dedup opt-out when counts are requested.

Coordinate so the two issues don't contradict. (A) is the clean end state.

πŸ”§ Approach (assuming option A)

  1. In record_line, when a (file,line) is first seen, record it; on repeat, increment an in-memory counter keyed file:line rather than appending a duplicate record.
  2. On flush, write file:line:count (extend the record format; keep backward-compatible parsing or bump the data-file format guarded by a version marker).
  3. Report layer: aggregate counts across the run (and across --parallel per-$$ files at merge), expose per-line counts in HTML (heat/number column) and optionally a --coverage-mode flag to show counts in the console.
  4. Keep the existing boolean summary (coverage %) unchanged β€” counts are additive detail.

βœ… Acceptance criteria

  • Reports can show, per line: run count (0 = uncovered).
  • --parallel merge sums counts across worker files correctly.
  • Coverage percentage and the covered-line set are unchanged vs. current output.
  • Data-file format change (if any) is versioned and parsed safely.

πŸ§ͺ TDD / measurement

  • RED: fixture with a line executed a known number of times (e.g. a 5-iteration loop) β†’ assert the report attributes count 5 to that line; a once-run line β†’ 1; an unrun line β†’ 0.
  • --parallel: two workers each hitting the same shared-source line β†’ summed count.
  • Gate: ./bashunit tests/, ./bashunit --parallel --simple --strict tests/, make sa, make lint. Regenerate coverage HTML/report snapshots if any.

⛓️ Constraints

  • Bash 3.0+ β€” in-memory count tally via the existing string-cache pattern (bounded by distinct lines); no ${var//} over big strings (perf-fork-budget.md).
  • Don't regress the dedup I/O win β€” option (A) keeps both.
  • If it materially adds record_line overhead, gate counts behind a flag (e.g. BASHUNIT_COVERAGE_COUNTS=true) so default coverage stays lean.

πŸ“ˆ Impact

Turns coverage from "covered?" into "how hot?" β€” useful for spotting under-exercised branches and dead-ish code. Depends on / must be sequenced with the per-line dedup issue.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions